I have a list of features/polygons that I need to run an operation on based on the maps bounding box relative to the feature, without moving the map.
To further explain my problem I can give an example of how I could do this if moving the map around wasn't an issue:
features.forEach(function (feature) {
var bbox,
ne,
sw,
fBounds,
zoom,
mBounds;
bbox = feature.geometry.bbox;
sw = L.latLng(bbox[1], bbox[0]);
ne = L.latLng(bbox[3], bbox[2]);
fBounds = L.latLngBounds(sw, ne);
map.fitBounds(bounds);
mBounds = map.getBounds();
zoom = map.getZoom();
//Execute operation based on mBounds and zoom
}
I have tested a lot and this is the closest thing I have to a working code snippet:
var self = this,
bbox,
sw,
ne,
bounds,
zoom,
swPoint,
nePoint,
center,
factor,
dw,
dh,
cpx;
bbox = feature.geometry.bbox;
sw = L.latLng(bbox[1], bbox[0]);
ne = L.latLng(bbox[3], bbox[2]);
bounds = L.latLngBounds(sw, ne);
zoom = self.map.getBoundsZoom(bounds, false); //maxZoom?
swPoint = self.map.project(bounds.getSouthWest(), zoom),
nePoint = self.map.project(bounds.getNorthEast(), zoom),
center = self.map.unproject(swPoint.add(nePoint).divideBy(2), zoom);
factor = self.map.options.crs.scale(zoom) / 8388608;
dw = self.map.getSize().x / 2*factor;
dh = self.map.getSize().y / 2*factor;
cpx = self.map.options.crs.latLngToPoint(center, zoom);
return {
ne: self.map.options.crs.pointToLatLng(L.point(cpx.x + dw, cpx.y - dh, false), zoom),
sw: self.map.options.crs.pointToLatLng(L.point(cpx.x - dw, cpx.y + dh, false), zoom),
center: center,
zoom: zoom
}
//Execute operation based on returned object, repeat for every feature
This 'works' but it doesn't give the same result as the first code snippet (i.e. the results are wrong).
Following snippet worked for me, in case anyone else should have the same question:
var self = this,
bbox,
sw,
ne,
bounds,
zoom,
center;
bbox = feature.geometry.bbox;
sw = L.latLng(bbox[1], bbox[0]);
ne = L.latLng(bbox[3], bbox[2]);
bounds = L.latLngBounds(sw, ne);
zoom = self.map.getBoundsZoom(bounds, false); //maxZoom?
sw = self.map.project(bounds.getSouthWest(), zoom),
ne = self.map.project(bounds.getNorthEast(), zoom),
center = self.map.unproject(sw.add(ne).divideBy(2), zoom);
bounds = self.map.getPixelBounds(center, zoom),
sw = self.map.unproject(b2.getBottomLeft()),
ne = self.map.unproject(b2.getTopRight());
return new L.LatLngBounds(sw, ne);
Related
I have written a function to retrieve pixel data from an openlayers map:
var imagery = new ol.layer.Tile({
source: new ol.source.OSM(),
crossOrigin: 'anonymous'
});
var context;
/**
* Apply a filter on "postcompose" events.
*/
imagery.on('postcompose', function(event) {
context = event.context;
});
function getPixel( latitude, longitude ){
let lat = parseFloat( latitude );
let lon = parseFloat( longitude );
var coord = ol.proj.transform( [lon, lat], 'EPSG:4326', 'EPSG:3857' );
var pixel = map.getPixelFromCoordinate( coord );
if( context == null )
return;
return context.getImageData(pixel[0], pixel[1], 1, 1).data;
}
var map = new ol.Map({
target: 'map',
layers: [imagery],
...
});
The code is based on this link. Also, the transformation from LatLon data to pixel coordinates is used quite extensively and does not provide any problems.
However, the pixel data that is retrieved is somehow offset from the coordinates, so that I see water where there is land and otherwise. As far as I can tell, the offset is a few hundred pixels (or a few meters in lonlat) in x and y direction, and probably the cause lies in the fact that the pixels from the map do not correspond to that of the canvas.
Does anyone have a clue on how to tackle this problem?
Mike's response indeed put me on the right track!
I have included the modified code for those who may run into the same issue:
var pixelRatio;
var context;
var imagery = new ol.layer.Tile({
source: new ol.source.OSM(),
crossOrigin: 'anonymous'
});
/**
* before rendering the layer, determine the pixel ratio
* and save the context
*/
imagery.on('precompose', function(event) {
context = event.context;
pixelRatio = event.frameState.pixelRatio;
context.save();
});
/**
* Restore the context
*/
imagery.on('postcompose', function(event) {
context = event.context;
context.restore();
});
/**
* Get the pixel's rgba data
*/
function getPixel( latitude, longitude, offx, offy ){
let lat = parseFloat( latitude );
let lon = parseFloat( longitude );
var coord = ol.proj.transform( [lon, lat], 'EPSG:4326', 'EPSG:3857' );
var pixel = map.getPixelFromCoordinate( coord );
if( context == null )
return;
var pixelAtClick = context.getImageData(pixel[0]*pixelRatio, pixel[1]*pixelRatio, 1, 1).data;
rgb = [0,0,0,0];
for( var i=0;i<pixelAtClick.length;i++ ){
rgb[i] = pixelAtClick[i];
}
return rgb;
}
The mxgraph Google Maps example (and demo) shows a simple implementation that create a Google Map overlay.
However I cannot figure out the correlation between the vertex pixel co-ordinates and the equivalent lat/long co-ordinates.
Line 140 of the example shows some hard-coded pixel co-ordinates (23,23) for Seattle:
var n1 = graph.insertVertex(parent, null, {label:'Seattle'}, 23, 23, 10, 10);
I want to display a geographic data set (i.e. I have latitude/longitude) but I can not figure out the conversion to mxGraph vertex pixel co-ordinates.
The Google API's "fromLatLngToDivPixel" does not work (it returns the lat/long of the centre of the map as 0,0), neither does "fromLatLngToContainerPixel".
Even weirder, a vertex with co-ordinates of 0,0 does not appear in the top left corner of the map (it is a little inside the map).
So it's a little complicated.
I finally created two classes to help out:
// Represent a map pixel
class PixelPos {
constructor(x,y) {
this._x = x;
this._y = y;
}
get x(){
return this._x;
}
get y(){
return this._y;
}
}
// Handles converting between lat/log and pixels
// Based on the initial created map div container.
class CoordTranslator {
constructor(graph, projection, bounds) {
this._graph = graph;
this._projection = projection;
this._bounds = bounds;
//
var swb = this._bounds.getSouthWest();
var neb = this._bounds.getNorthEast();
this._neb_lat = neb.lat();
this._swb_lng = swb.lng();
this._map_width = neb.lng() - swb.lng();
this._map_height = neb.lat() - swb.lat();
// Get pixel values from the Geo boundary box
var sw = this._projection.fromLatLngToDivPixel(swb);
var ne = this._projection.fromLatLngToDivPixel(neb);
// Map pixel width and Height
this._pix_width = (ne.x - sw.x);
this._pix_height = (sw.y - ne.y);
this._scale = graph.view.getScale();
}
// Convert the provided lat/long into a PixelPos
convert(lat, lng){
var x = (lng-this._swb_lng)/this._map_width;
x = x*this._pix_width;
x = x/this._scale;
var y = (this._neb_lat - lat)/this._map_height;
y = y*this._pix_height;
y = y/this._scale;
// And, for some unknown reason, I have to magic a final offset!!
return new PixelPos(x-5,y-3);
}
}
I initialise the CoordTranslator class in the mxGraphOverlay.prototype.draw:
mxGraphOverlay.prototype.draw = function()
{
if (drawn = 0){
drawn = 1;
....
let ct = new CoordTranslator(this.graph_, overlayProjection, this.bounds_);
....
// 6193351 # 46.8127123,14.5866472
var pp = ct.convert(46.8127123, 14.58664720);
var n3 = graph.insertVertex(parent, null, {label:'6193351'}, pp.x, pp.y, 10, 10);
....
}
}
There are probably better ways to initialise and use the CoordTranslator, but the ideas are here.
I'm trying to draw a hexagonal grid in Google Maps. I've come up with a solution based off this answer which looks fine at higher zooms, but when zoomed further out I find that the classic "orange-peel" problem occurs: The hexagons no longer fit together like they should:
I'm using this rather cool geodesy library to calculate hexagon centers based on an ellipsoidal model (since a 2d model clearly doesn't work on a real-world map) but it's still looking pretty bad when zoomed out.
Preferably, I'd like to draw the hexagons in such a way that they are exactly the same shape and size on screen.
Here's the code I've been working with, also available as a Plunker here. I've tried calculating the vertices of each polygon using the same geodesy library that I'm using to calculate the polygon centers, but it still doesn't look right when zoomed out.
var hexgrid = [];
function initialize(){
// Create the map.
var map = new google.maps.Map(document.getElementById('map'), {
center: {lat: 51.5, lng: 0},
scrollwheel: true,
zoom: 8
});
// This listener waits until the map is done zooming or panning,
// Then clears all existing polygons and re-draws them.
map.addListener('idle', function() {
// Figure out how big our grid needs to be
var spherical = google.maps.geometry.spherical,
bounds = map.getBounds(),
cor1 = bounds.getNorthEast(),
cor2 = bounds.getSouthWest(),
cor3 = new google.maps.LatLng(cor2.lat(), cor1.lng()),
cor4 = new google.maps.LatLng(cor1.lat(), cor2.lng()),
diagonal = spherical.computeDistanceBetween(cor1,cor2),
gridSize = diagonal / 20;
// Determine the actual distance between tiles
var d = 2 * gridSize * Math.cos(Math.PI / 6);
// Clear all the old tiles
hexgrid.forEach(function(hexagon){
hexagon.setMap(null);
});
hexgrid = [];
// Determine where the upper left-hand corner is.
bounds = map.getBounds();
ne = bounds.getNorthEast();
sw = bounds.getSouthWest();
var point = new LatLon(ne.lat(), sw.lng());
// ... Until we're at the bottom of the screen...
while(point.lat > sw.lat()){
// Keep this so that we know where to return to when we're done moving across to the right
leftPoint = new LatLon(point.lat, point.lon).destinationPoint(d, 150).destinationPoint(d, 210).destinationPoint(d, 270).destinationPoint(d, 90)
step = 1;
while(point.lon < ne.lng()){
// Use the modulus of step to determing if we want to angle up or down
if (step % 2 === 0){
point = new LatLon(point.lat, point.lon).destinationPoint(d, 30);
} else {
point = new LatLon(point.lat, point.lon).destinationPoint(d, 150);
}
step++; // Increment the step
// Draw the hexagon!
// First, come up with the corners.
vertices = [];
for(v = 1; v < 7; v++){
angle = v * 60;
vertex = point.destinationPoint(d / Math.sqrt(3), angle);
vertices.push({lat: vertex.lat, lng: vertex.lon});
}
// Create the shape
hexagon = new google.maps.Polygon({
map: map,
paths: vertices,
strokeColor: '#090',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: '#090',
fillOpacity: 0.1,
draggable: false,
});
// Push it to hexgrid so we can delete it later
hexgrid.push(hexagon)
}
// Return to the left.
point = leftPoint;
}
});
}
google.maps.event.addDomListener(window, 'load', initialize);
Please consider that Google Maps is in Mercator Projection.
You have to compensate for the sphere of the globe on the projection.
https://en.wikipedia.org/wiki/Mercator_projection
At the moment I make website for a company who wants a custom map on their page. This map is a big a vector map drawn by a graphic designer. So I use leaflet for this but I have an issue. I make it full screen. The issue is I set the bounds and everything and it works on all side except the bottom so when i start moving up and it not goes back to the bottom. The funny thing if I resize the window so for example I make it small and after back to full window size the bottom part works perfectly.
Here is my code so far:
var winh = $(window).height();
var winw = $(window).width();
var maph = $('#map').height();
var mapw = $('#map').width();
var offset = $('#map').offset();
var centerX = offset.left + mapw / 2;
var centerY = offset.top + maph / 2;
var changem = false;
var cbut = $('#building');
var southWest = new L.LatLng(winh, 0);
var northEast = new L.LatLng(0, winw);
var bounds = L.latLngBounds(southWest, northEast);
var map = L.map('map', {
maxBounds: bounds,
maxZoom: 2,
minZoom: 0,
crs: L.CRS.Simple
}).setView([winh, winw], 0);
// .setView([winh, winw], 0)
map.setMaxBounds(bounds);
// map.panTo(new L.LatLng(centerY,centerX));
// ----------------------------------
// Load the images for the map
// ----------------------------------
var imageUrl = 'http://rigailoveyou.exflairdigital.com/public/img/houses.png'
var imageUrl2 = 'http://rigailoveyou.exflairdigital.com/public/img/road.png'
var imageBounds = [[winh,0], [0,winw]];
var map1 = L.imageOverlay(imageUrl, bounds,'Riga I Love You').addTo(map);
var map2 = L.imageOverlay(imageUrl2, bounds).addTo(map);
I want to add a marker every 5 or 10 Kilometer on the polylines of the direction given by google maps api.
something like this :
http://www.geocodezip.com/v3_polyline_example_kmmarkers_0.html
but with the google direction's
I found a formula that works like a charm. It adds a marker every 8 meter between to given points.
I got the formula from here: How to calculate the points between two given points and given distance?
PointF pointA, pointB;
var diff_X = pointB.X - pointA.X;
var diff_Y = pointB.Y - pointA.Y;
int pointNum = 8;
var interval_X = diff_X / (pointNum + 1);
var interval_Y = diff_Y / (pointNum + 1);
List<PointF> pointList = new List<PointF>();
for (int i = 1; i <= pointNum; i++)
{
pointList.Add(new PointF(pointA.X + interval_X * i, pointA.Y + interval_Y*i));
}
Android
My end result translation
//GeoPoint PointF, pointA, pointB;
Double diff_X = lat2 - lat1;
Double diff_Y = lon2 - lon1;
int pointNum = 8;
Double interval_X = diff_X / (pointNum + 1);
Double interval_Y = diff_Y / (pointNum + 1);
//ArrayList<GeoPoint> geoPoints = new ArrayList<>();
List<GeoPoint> pointList = new ArrayList<>();
for (int i = 1; i <= pointNum; i++)
{
GeoPoint g = new GeoPoint(lat1 + interval_X * i, lon1 + interval_Y*i);
pointList.add(g);
itemizedLayer.addItem(createMarkerItem(g, R.drawable.ic_my_location));
}
map.map().updateMap(true);
Given a start point, initial bearing, and distance, this will
calculate the destination point and final bearing travelling along a
(shortest distance) great circle arc.
var lat2 = Math.asin( Math.sin(lat1)*Math.cos(d/R) +
Math.cos(lat1)*Math.sin(d/R)*Math.cos(brng) );
var lon2 = lon1 + Math.atan2(Math.sin(brng)*Math.sin(d/R)*Math.cos(lat1),
Math.cos(d/R)-Math.sin(lat1)*Math.sin(lat2));
source: http://www.movable-type.co.uk/scripts/latlong.html
The radius of the earth (R) is 6371000 meters.
brng is the direction you are travelling in degrees (0 = north).
Then use this function to add markers to the map
function setMarkers(map, locations) {
// Add markers to the map
// Marker sizes are expressed as a Size of X,Y
// where the origin of the image (0,0) is located
// in the top left of the image.
// Origins, anchor positions and coordinates of the marker
// increase in the X direction to the right and in
// the Y direction down.
var image = {
url: 'images/beachflag.png',
// This marker is 20 pixels wide by 32 pixels tall.
size: new google.maps.Size(20, 32),
// The origin for this image is 0,0.
origin: new google.maps.Point(0,0),
// The anchor for this image is the base of the flagpole at 0,32.
anchor: new google.maps.Point(0, 32)
};
var shadow = {
url: 'images/beachflag_shadow.png',
// The shadow image is larger in the horizontal dimension
// while the position and offset are the same as for the main image.
size: new google.maps.Size(37, 32),
origin: new google.maps.Point(0,0),
anchor: new google.maps.Point(0, 32)
};
// Shapes define the clickable region of the icon.
// The type defines an HTML <area> element 'poly' which
// traces out a polygon as a series of X,Y points. The final
// coordinate closes the poly by connecting to the first
// coordinate.
var shape = {
coord: [1, 1, 1, 20, 18, 20, 18 , 1],
type: 'poly'
};
for (var i = 0; i < locations.length; i++) {
var beach = locations[i];
var myLatLng = new google.maps.LatLng(beach[1], beach[2]);
var marker = new google.maps.Marker({
position: myLatLng,
map: map,
shadow: shadow,
icon: image,
shape: shape,
title: beach[0],
zIndex: beach[3]
});
}
}
source:
https://developers.google.com/maps/documentation/javascript/overlays
Edit that function to have the proper marker icons and call it for each marker you want to place.