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);
Related
I have a leaflet map which consists of a tile layer and above an imageOverlay which is semi transparently showing temperature distribution as colored areas. The Overlay is placed at certain bounds within the tile layer.
When I click somewhere on the the overlay, I want to figure out what color the pixel at that point has.
My problem is to project the clicked position onto the imageOverlay respecting the offset of the imageOverlay to the visible map and the zoom level. Eventually I want to get the pixel coordinates at the image (at it's natural resolution)
The code roughly looks like this:
var imgUrl = 'https://somewhere.somewhere/myImage.png';
var tilesUrl = 'https://somewhere.somewhere/{z}/{x}/{y}.png';
var tilesBounds = [...];
var imgBounds = [...];
var latlng = [...];
var mymap = L.map('mapid').setView(latlng, 9);
L.tileLayer(tilesUrl, {
attribution: 'TILES',
maxZoom: 12,
minZoom: 7,
id: 'tiles',
tms: true,
maxBounds: tilesBounds
}).addTo(mymap);
var imgOverlay = L.imageOverlay(imgUrl, imgBounds {
attribution: 'dataimg',
opacity: '0.4',
id: 'dataImg',
interactive: true
}).addTo(mymap);
imgOverlay.on('click',
(e) => {
var x = ???;
var y = ???;
var color = getColorAt(x, y);
}
)
One possible solution would be to
get the coordinates of the mouse relative to the image at its current size
and then cross multiply to determine the coordinates relative to the original size of the image
For example, imgWidth and imgHeight being the original dimensions of the image:
imgOverlay.on('click', (leafletEvent) => {
// see https://stackoverflow.com/a/42111623/1071630
var e = leafletEvent.originalEvent;
var rect = e.target.getBoundingClientRect();
var zoomedX = e.clientX - rect.left; //x position within the element.
var zoomedY = e.clientY - rect.top; //y position within the element
const x = Math.round(zoomedX * imgWidth / rect.width);
const y = Math.round(zoomedY * imgHeight / rect.height);
console.log(x, y);
});
var map = L.map('map').setView([48.854, 2.2922926], 14);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
const bounds = [[48.85, 2.28], [48.86, 2.29]]
const imgOverlay = L.imageOverlay('https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Tour_Eiffel_en_mai_2014.JPG/450px-Tour_Eiffel_en_mai_2014.JPG', bounds, {
attribution: 'Nicolas Halftermeyer / CC BY-SA (https://creativecommons.org/licenses/by-sa/3.0)',
id: 'dataImg',
interactive: true
}).addTo(map);
const imgWidth = 450, imgHeight = 600;
imgOverlay.on('click', (leafletEvent) => {
// see https://stackoverflow.com/a/42111623/1071630
var e = leafletEvent.originalEvent;
var rect = e.target.getBoundingClientRect();
var zoomedX = e.clientX - rect.left; //x position within the element.
var zoomedY = e.clientY - rect.top; //y position within the element
const x = Math.round(zoomedX * imgWidth / rect.width);
const y = Math.round(zoomedY * imgHeight / rect.height);
document.getElementById('log').innerHTML+= x+","+y+"<br>";
});
#map {width: 400px; height: 200px;}
#log {position: absolute; top: 0; right:10px; width: 100px;}
<script src="https://unpkg.com/leaflet#1.6.0/dist/leaflet.js"></script>
<link href="https://unpkg.com/leaflet#1.6.0/dist/leaflet.css" rel="stylesheet"/>
<div style='position: relative'>
<div id="map"></div>
<div id='log'></div>
</div>
I had drawn an animation in canvas like this and rendered a map using openlayers4. I want to add this canvas to the map[openlayers canvas] in next step.
I had used ol.source.ImageCanvas add a boundary to openlayers, so I try to add the canvas with animation using ImageCanvas, but failed.
What's more, openlayers API said ol.source.ImageCanvas method only the image canvas can be added. I didn't know whether the animate canvas so does.
Should I insit on using ImageCanvas method or try others?
Can someone give me an example if I abandon the ImageCanvas method?
After some tries, I got a solution! Haha!
First: the ol.source.ImageCanvas can still use, but you will get a stopped animate just like a screenshot.
Second: must know the ol.map.render() in openlayers3 or openlayers4, whose description is:
Request a map rendering (at the next animation frame).
Thus, you can use it to refresh the map and get the next animation of canvas.
The following is snippets of my code:
var topoCanvas = function(extent, resolution, pixelRatio, size, projection) {
// topo features;
var features = topojson.feature(tokyo, tokyo.objects.counties);
var canvasWidth = size[0];
var canvasHeight = size[1];
var canvas = d3.select(document.createElement('canvas'));
canvas.attr('width', canvasWidth).attr('height', canvasHeight);
var context = canvas.node().getContext('2d');
var d3Projection = d3.geo.mercator().scale(1).translate([0, 0]);
var d3Path = d3.geo.path().projection(d3Projection);
var pixelBounds = d3Path.bounds(features);
var pixelBoundsWidth = pixelBounds[1][0] - pixelBounds[0][0];
var pixelBoundsHeight = pixelBounds[1][1] - pixelBounds[0][1];
var geoBounds = d3.geo.bounds(features);
var geoBoundsLeftBottom = ol.proj.transform(geoBounds[0], 'EPSG:4326', projection);
var geoBoundsRightTop = ol.proj.transform(geoBounds[1], 'EPSG:4326', projection);
var geoBoundsWidth = geoBoundsRightTop[0] - geoBoundsLeftBottom[0];
if (geoBoundsWidth < 0) {
geoBoundsWidth += ol.extent.getWidth(projection.getExtent());
}
var geoBoundsHeight = geoBoundsRightTop[1] - geoBoundsLeftBottom[1];
var widthResolution = geoBoundsWidth / pixelBoundsWidth;
var heightResolution = geoBoundsHeight / pixelBoundsHeight;
var r = Math.max(widthResolution, heightResolution);
var scale = r / (resolution / pixelRatio);
var center = ol.proj.transform(ol.extent.getCenter(extent), projection, 'EPSG:4326');
d3Projection.scale(scale).center(center).translate([canvasWidth / 2, canvasHeight / 2]);
d3Path = d3Path.projection(d3Projection).context(context);
d3Path(features);
context.stroke();
// above code is add a topoJson boundary to canvas
// below code is add an animation to canvas
var settings = createSettings(tokyo, {
width: canvasWidth,
height: canvasHeight
});
// reset the projection and bounds for animation canvas
settings.projection = d3Projection;
settings.bounds = geoBounds;
var mesh = buildMeshes(tokyo, settings);
when(render(settings, mesh, {
width: canvasWidth,
height: canvasHeight
})).then(function(masks) {
when(interpolateField(stations, data, settings, masks)).then(function(field) {
// wind moving animation
animate(settings, field, canvas);
// refresh the map to get animation
window.setInterval(function() {
map.render();
}, 50);
});
});
return canvas[0][0];
}
I was using this function, which given a latlong, it centers the google maps considering the offsetX and offsetY:
customCenter: function ( latlng, offsetx, offsety ) {
// latlng is the apparent centre-point
// offsetx is the distance you want that point to move to the right, in pixels
// offsety is the distance you want that point to move upwards, in pixels
var self = this;
var scale = Math.pow(2, self.map.getZoom());
google.maps.event.addListenerOnce(self.map,"projection_changed", function() {
var worldCoordinateCenter = self.map.getProjection().fromLatLngToPoint(latlng);
var pixelOffset = new google.maps.Point((offsetx/scale) || 0,(offsety/scale) ||0)
var worldCoordinateNewCenter = new google.maps.Point(
worldCoordinateCenter.x - pixelOffset.x,
worldCoordinateCenter.y + pixelOffset.y
);
var newCenter = self.map.getProjection().fromPointToLatLng(worldCoordinateNewCenter);
self.map.setCenter(newCenter);
});
}
Which I'm using like, so:
self.customCenter (marker.position, $('aside').width(), 0 );
But now they'are asking me to fitbounds with the offset ( so none of the Markers are out of the view
(usually used like so:)
var bounds = new google.maps.LatLngBounds();
bounds.extend(myPlace);
bounds.extend(Item_1);
bounds.extend(Item_2);
bounds.extend(Item_3);
self.map.fitBounds(bounds);
But I don't see a way to combine this two functionalities
( this is because I have an aside floating ( pos: absolute ) over the google maps, just like this image I found online:
)
Any ideas?
-I'm trying like this now:
customCenterWithBounds: function ( offsetx, offsety ) {
// offsetx is the distance you want that point to move to the right, in pixels
// offsety is the distance you want that point to move upwards, in pixels
var self = this;
self.map.fitBounds(self.bounds);
var mapCenter = self.map.getCenter();
var latlng = new google.maps.LatLng (mapCenter.lat(), mapCenter.lng() );
var scale = Math.pow(2, self.map.getZoom());
google.maps.event.addListenerOnce(self.map,"projection_changed", function() {
var worldCoordinateCenter = self.map.getProjection().fromLatLngToPoint(latlng);
var pixelOffset = new google.maps.Point((offsetx/scale) || 0,(offsety/scale) ||0)
var worldCoordinateNewCenter = new google.maps.Point(
worldCoordinateCenter.x - pixelOffset.x,
worldCoordinateCenter.y + pixelOffset.y
);
var newCenter = self.map.getProjection().fromPointToLatLng(worldCoordinateNewCenter);
self.map.setCenter(newCenter);
});
}
But the map would be over-zoomed.. and not even in the center of the visible area..
I ended up doing this
customCenter: function (offsetx, offsety, bounds_obj ) {
if ($(window).width() <= 1200 ) {
offsetx = 0;
}
var self = this;
google.maps.event.addListenerOnce(self.map,"projection_changed", function() {
var latlng;
var fbObj = self.map.fitBounds(self.bounds);
var mapCenter = self.map.getCenter();
latlng = new google.maps.LatLng(mapCenter.lat(), mapCenter.lng());
var scale = Math.pow(2, self.map.getZoom());
var worldCoordinateCenter = self.map.getProjection().fromLatLngToPoint(latlng);
var pixelOffset = new google.maps.Point((offsetx/scale) || 0,(offsety/scale) ||0)
var worldCoordinateNewCenter = new google.maps.Point(
worldCoordinateCenter.x - pixelOffset.x,
worldCoordinateCenter.y + pixelOffset.y
);
var newCenter = self.map.getProjection().fromPointToLatLng(worldCoordinateNewCenter);
self.map.setCenter(newCenter);
});
}
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);
I'm using the Google Maps API to embed a map in a web page. The map fills the entire screen, but there's ~400px worth of content on the left side of the screen that mostly covers the map. When I want to pan the map, that area to the left should be treated as though it isn't visible.
I came up with the following code to calculate the "usable part" of the map, which I'd like to be 50px in from the map's edge, and should also avoid the 400px area to the left side of the map:
panIfNotClose = function(latLngs){
if(latLngs.length === 0)
return;
// Get some important values that may not be available yet
// http://stackoverflow.com/a/2833015/802335
var bounds = map.getBounds();
if(!bounds)
return setTimeout(panIfNotClose.bind(this, latLngs), 10);
var overlay = new google.maps.OverlayView();
overlay.draw = function(){};
overlay.setMap(map);
var proj = overlay.getProjection();
if(!proj)
return setTimeout(panIfNotClose.bind(this, latLngs), 10);
// Calculate the "usable part" of the map
var center = bounds.getCenter();
var northEast = bounds.getNorthEast();
var southWest = bounds.getSouthWest();
var northEastPx = proj.fromLatLngToContainerPixel(northEast);
var southWestPx = proj.fromLatLngToContainerPixel(southWest);
var menuPadding = 400;
var newNorthEastPx = new google.maps.Point(northEastPx.x + 50, northEastPx.y + 50);
var newSouthWestPx = new google.maps.Point(southWestPx.x - (50 + menuPadding), southWestPx.y - 50);
var newNorthEast = proj.fromContainerPixelToLatLng(newNorthEastPx);
var newSouthWest = proj.fromContainerPixelToLatLng(newSouthWestPx);
var centerBounds = new google.maps.LatLngBounds(newSouthWest, newSouthWest);
// Decide if any of the new LatLngs are far enough away that the map should move
var shouldMove = false;
var targetBounds = new google.maps.LatLngBounds();
for(var i = 0; i < latLngs.length; i++){
targetBounds.extend(latLngs[i]);
if(!centerBounds.contains(latLngs[i]))
shouldMove = true;
}
// If the LatLngs aren't all near the center of the map, pan to it
if(latLngs.length === 1){
if(shouldMove || map.getZoom() !== 18)
map.panTo(latLngs[0]);
map.setZoom(18);
}else{
var targetZoom = Math.min(getBoundsZoomLevel(targetBounds), 18);
if(shouldMove || map.getZoom() !== targetZoom)
map.panTo(targetBounds.getCenter());
map.setZoom(targetZoom);
}
}
This code should test the "valid" area to make sure all the given LatLngs fit inside, but it doesn't yet make any changes to panTo to "move" the center 200px to the right to account for the 400px worth of content on the left.
The code doesn't work as I intended, but I'm not sure why. I suspect I'm probably doing something wrong when converting from a LatLng to a Point or vice-versa. I may also be doing far more work than is necessary, although I can't think of a way to simplify it.
This turned out to be a pretty simple mistake. In the line var centerBounds = new google.maps.LatLngBounds(newSouthWest, newSouthWest);, I accidentally used the same LatLng twice, rather than the northeast and southwest corners. I also had a couple simple arithmetic issues when calculating newNorthEastPx and newSouthWestPx. Drawing a google.maps.Rectangle using centerBounds helped work that out quickly and easily. For anyone interested, here's the end result:
function panIfNotClose(latLngs, zoomOnly){
if(latLngs.length === 0)
return;
if(typeof latLngs !== "object")
latLngs = [latLngs];
// Calculate the "middle half" of the map
var bounds = map.getBounds();
if(!bounds) // http://stackoverflow.com/a/2833015/802335
return setTimeout(panIfNotClose.bind(this, latLngs, zoomOnly), 10);
var overlay = new google.maps.OverlayView();
overlay.draw = function(){};
overlay.setMap(map);
var proj = overlay.getProjection();
if(!proj)
return setTimeout(panIfNotClose.bind(this, latLngs, zoomOnly), 10);
var center = bounds.getCenter();
var northEast = bounds.getNorthEast();
var southWest = bounds.getSouthWest();
var northEastPx = proj.fromLatLngToContainerPixel(northEast);
var southWestPx = proj.fromLatLngToContainerPixel(southWest);
var menuPadding = 0;
if($navBar.css("display") !== "none")
menuPadding = 400;
var newNorthEastPx = new google.maps.Point(northEastPx.x - 100, northEastPx.y + 100);
var newSouthWestPx = new google.maps.Point(southWestPx.x + (100 + menuPadding), southWestPx.y - 100);
var newNorthEast = proj.fromContainerPixelToLatLng(newNorthEastPx);
var newSouthWest = proj.fromContainerPixelToLatLng(newSouthWestPx);
centerBounds = new google.maps.LatLngBounds(newSouthWest, newNorthEast);
var shouldMove = false;
var targetBounds = new google.maps.LatLngBounds();
for(var i = 0; i < latLngs.length; i++){
targetBounds.extend(latLngs[i]);
if(!centerBounds.contains(latLngs[i]))
shouldMove = true;
}
// If the marker isn't near the center of the map, pan to it
if(latLngs.length === 1){
if(!zoomOnly && (shouldMove || map.getZoom() !== 18))
map.panTo(correctCenter(latLngs[0], proj));
map.setZoom(18);
}else{
var targetZoom = Math.min(getBoundsZoomLevel(targetBounds), 18);
if(!zoomOnly && (shouldMove || map.getZoom() !== targetZoom))
map.panTo(correctCenter(targetBounds.getCenter(), proj));
map.setZoom(targetZoom);
}
}
function correctCenter(latLng, proj){
// $navBar references a jQuery pointer to a DOM element
if($navBar.css("display") === "none")
return latLng;
var latLngPx = proj.fromLatLngToContainerPixel(latLng);
var newLatLngPx = new google.maps.Point(latLngPx.x - 200, latLngPx.y)
return proj.fromContainerPixelToLatLng(newLatLngPx);
}
// Adapted from http://stackoverflow.com/a/13274361/802335
function getBoundsZoomLevel(bounds){
// $mapCanvas is a jQuery reference to the div containing the Google Map
var mapDim = {
width: $mapCanvas.width() * .6,
height: $mapCanvas.height() * .6
};
var ZOOM_MAX = 18;
function latRad(lat){
var sin = Math.sin(lat * Math.PI / 180);
var radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;
}
function zoom(mapPx, fraction){
return Math.floor(Math.log(mapPx / 256 / fraction) / Math.LN2);
}
var ne = bounds.getNorthEast();
var sw = bounds.getSouthWest();
var latFraction = (latRad(ne.lat()) - latRad(sw.lat())) / Math.PI;
var lngDiff = ne.lng() - sw.lng();
var lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;
var latZoom = zoom(mapDim.height, latFraction);
var lngZoom = zoom(mapDim.width, lngFraction);
return Math.min(latZoom, lngZoom, ZOOM_MAX);
}