user1518183
user1518183

Reputation: 4771

Calculate bounding box for OpenStreetMap

Is possible to get bounding box from coordinates (latitude, longitude), zoom level and size (screen)? I found only calculating bounding box from tile. I need it, because OpenStreetMap accept only bbox in export mode. I dont have any experience with this, so I will be glad for any advice. Thanks.

Edit:

Sorry, I was describe it wrong. I write something like this:

(pseudocode) `

x = getX(longitude, zoom); //X Tile - return 41870
y = getY(latitude, zoom); //Y Tile - return 22226
north = getXToLongitude(x, zoom); //return 49.998779 
south = getXToLongitude(x + 1, zoom); //return 50.004272
west = getYToLatitude(y, zoom); //return 49.997078 
east = getYToLatitude(y + 1, zoom); //return 50.000609

`

But this is very inaccurate and and shifts the center of the 100 m.

Upvotes: 2

Views: 7853

Answers (2)

Ihor Pomaranskyy
Ihor Pomaranskyy

Reputation: 5611

I tried to implement Angular component for OSM embedding, based on code samples from OSM. It turned out to be pretty hard. :(

Here is the code I have so far:

import { Component, Input, OnInit } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';

@Component({
  selector: 'app-osm',
  templateUrl: './osm.component.html',
  styleUrls: ['./osm.component.scss']
})
export class OsmComponent implements OnInit {
  @Input() latitude: number;
  @Input() longitude: number;
  @Input() zoom: number;

  public iframeSrc;
  public extMapSrc;

  constructor(
    private sanitizer: DomSanitizer
  ) { }

  ngOnInit(): void {
    const bbox = this.boundaryBox(this.latitude, this.longitude, 1100, 400, 16);
    this.iframeSrc = this.sanitizer.bypassSecurityTrustResourceUrl(
      `https://www.openstreetmap.org/export/embed.html` +
      `?bbox=${bbox.join(',')}` +
      `&layer=mapnik` +
      `&marker=${this.latitude},${this.longitude}`
    );
    this.extMapSrc = this.sanitizer.bypassSecurityTrustUrl(
      `https://www.openstreetmap.org/` +
      `?mlat=${this.latitude}&mlon=${this.longitude}` +
      `#map=17/${this.latitude}/${this.longitude}`
    );
  }

  lonToTile(lon: number, zoom: number): number {
    return (Math.floor((lon + 180) / 360 * Math.pow(2, zoom)));
  }

  latToTile(lat: number, zoom: number): number {
    return Math.floor(
      (1 - Math.log(
        Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)
      ) / Math.PI) / 2 * Math.pow(2, zoom)
    );
  }

  tileToLon(x: number, zoom: number): number {
    return (x / Math.pow(2, zoom) * 360 - 180);
  }

  tileToLat(y: number, zoom: number): number {
    const n = Math.PI - 2 * Math.PI * y / Math.pow(2, zoom);
    return (180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))));
  }

  boundaryBox(
    lat: number,
    lon: number,
    width: number = 1110,
    height: number = 400,
    zoom: number = 17,
  ) {
    const tileSize = 256;

    const lonTile = this.lonToTile(lon, zoom);
    const latTile = this.latToTile(lat, zoom);

    const minLonTile = (lonTile * tileSize - width / 2) / tileSize;
    const minLatTile = (latTile * tileSize - height / 2) / tileSize;
    const maxLonTile = (lonTile * tileSize + width / 2) / tileSize;
    const maxLatTile = (latTile * tileSize + height / 2) / tileSize;

    const minLon = this.tileToLon(minLonTile, zoom);
    const minLat = this.tileToLat(minLatTile, zoom);
    const maxLon = this.tileToLon(maxLonTile, zoom);
    const maxLat = this.tileToLat(maxLatTile, zoom);

    return [minLon, minLat, maxLon, maxLat];
  }

}

It (kind of) works, though map positioning for some reason is not exactly correct.

Upvotes: 0

Matija Nalis
Matija Nalis

Reputation: 737

Yes, it is. I've had a similar problem, so I've written it up. Like this:

use Math::Trig;

sub getTileNumber {
    my ($lat,$lon,$zoom) = @_;
    my $xtile = int( ($lon+180)/360 * 2**$zoom ) ;
    my $ytile = int( (1 - log(tan(deg2rad($lat)) + sec(deg2rad($lat)))/pi)/2 * 2**$zoom ) ;
    return ($xtile, $ytile);
}

sub getLonLat {
    my ($xtile, $ytile, $zoom) = @_;
    my $n = 2 ** $zoom;
    my $lon_deg = $xtile / $n * 360.0 - 180.0;
    my $lat_deg = rad2deg(atan(sinh(pi * (1 - 2 * $ytile / $n))));
    return ($lon_deg, $lat_deg);
}

# convert from permalink OSM format like:
# http://www.openstreetmap.org/?lat=43.731049999999996&lon=15.79375&zoom=13&layers=M
# to OSM "Export" iframe embedded bbox format like:
# http://www.openstreetmap.org/export/embed.html?bbox=15.7444,43.708,15.8431,43.7541&layer=mapnik

sub LonLat_to_bbox {
    my ($lat, $lon, $zoom) = @_;

    my $width = 425; my $height = 350;  # note: must modify this to match your embed map width/height in pixels
    my $tile_size = 256;

    my ($xtile, $ytile) = getTileNumber ($lat, $lon, $zoom);

    my $xtile_s = ($xtile * $tile_size - $width/2) / $tile_size;
    my $ytile_s = ($ytile * $tile_size - $height/2) / $tile_size;
    my $xtile_e = ($xtile * $tile_size + $width/2) / $tile_size;
    my $ytile_e = ($ytile * $tile_size + $height/2) / $tile_size;

    my ($lon_s, $lat_s) = getLonLat($xtile_s, $ytile_s, $zoom);
    my ($lon_e, $lat_e) = getLonLat($xtile_e, $ytile_e, $zoom);

    my $bbox = "$lon_s,$lat_s,$lon_e,$lat_e";
    return $bbox;
}

I've also added this info to OSM wiki, so it will be easier to find in the future...

Upvotes: 4

Related Questions