Detect when user reaches maxBounds using Leaflet - javascript

I am using leaflet to show an interactive map to our users.
We want to let them browse through a limited area, and inform them they have to subscribe in case they want to see something too far away (using a pop up or equivalent).
So far I have seen that Leaflet supports a maxBounds option.
This is a good start that lets me prevent users to see larger areas.
Now I would like to be able to detect a maxBounds 'event' to show the user a pop up.
I have been looking into the Leaflet source code, but couldn't find an obvious way to do it.
so far I have found that the maxBounds option is fed into the setView method.
This method itself uses the _limitCenter method to define the center.
This goes a few levels deeper, down to the _getBoundsOffset method that finally uses the bounds.
_getBoundsOffset: function (pxBounds, maxBounds, zoom) {
var projectedMaxBounds = toBounds(
this.project(maxBounds.getNorthEast(), zoom),
this.project(maxBounds.getSouthWest(), zoom)
),
minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
dx = this._rebound(minOffset.x, -maxOffset.x),
dy = this._rebound(minOffset.y, -maxOffset.y);
return new Point(dx, dy);
},
The closest I could find so far would be to hook into the moveend event and check whether the center is out of my bounds manually.
However, it seems like this would be redundant with what leaflet is already doing.
Is there a better to leverage leaflet to achieve this?
Thanks

Just check if your defined bounds contain the map bounds. As long as the map bounds are inside the defined bounds, this will do nothing:
var myBounds = L.latLngBounds(...)
map.on('move moveend zoomend', function(){
if (!myBounds.contains(map.getBounds())) {
// Display popup or whatever
}
});
it seems like this would be redundant with what leaflet is already doing.
Don't worry about that. The overhead is negligible for this use case.

Related

Slow updating HereMaps DomIcon when location (lat / long) changes, re-render

Using renderToStaticMarkup to render html to a DomIcon in HereMaps. That's working and quick when there's not that many markers and updates. However, 100 markers frequently updating and re-rendering is causing a slow page open. It's even slowing the map render.
I've looked into best practices around re-using a DomIcon. I've also been looking into clustering -- but not sure how updating would work. Is clustering the only way to go further here? Curious if there's any other best practices for performance
Clustering solves the potential performance issues, however H.map.Group is used to associate the markers together and the group.getBounds() method to find the minimal bounding box holding all of the group's contents. The map.viewBounds() can then be updated.
function addMarkersAndSetViewBounds() {
// create map objects
var toronto = new H.map.Marker({lat:43.7, lng:-79.4}),
boston = new H.map.Marker({lat:42.35805, lng:-71.0636}),
washington = new H.map.Marker({lat:38.8951, lng:-77.0366}),
group = new H.map.Group();
// add markers to the group
group.addObjects([toronto, boston, washington]);
map.addObject(group);
// get geo bounding box for the group and set it to the map
map.getViewModel().setLookAtData({
bounds: group.getBoundingBox()
});
}
forEachDataPoint (callback)
This method invokes the specified callback for each data point in the given cluster which can work for updating the data points.
A clustering algorithm groups data points by collapsing two or more
points positioned close to one another on the screen into a single
cluster point. All other (not collapsed) points are still visible on
map as noise points.
If this actually suits the use case, go for clustering. It helps to uplift the performance issue. for more details refer :
developer.here.com/documentation/maps/topics/clustering.html

getPixelFromCoordinate returning null

In my app I added a mapquest layer with open layers 3, drawed points and lines...
Now i need to know the pixel coordinates from a long/lat point in the map (visible area) using
map.getPixelFromCoordinate(coordinate).
this function always returns null (testing):
center = map.getView().getCenter();
px = map.getPixelFromCoordinate(center);
alert(JSON.stringify(px));
What i'm doing wrong or what i didn't understand properly?
I found this answer:
I'd be careful with this. You might get wrong results, e.g. when the map does not have the final layout yet. It is better to wait with the first coordinate to pixel conversion until the map is rendered. You do not need a timeout for this, we have the 'postrender' event on ol.Map. So in your initialisation code, you could do something like this:
map.once('postrender', function() {
// safe to call map.getPixelFromCoordinate from now on
});
Source: github.com/openlayers/ol3/issues/5456
I hope this helps.

Openlayers 2. z-axis and select control

I've got a couple vector layers one has polygons, one has lines. We have a need to add lines to the line layer that attach to the polygons (database procedure requires the polygons IDs, which are stored in attributes on the polygons)
So I have a drawFeature control on the lineLayer, and a selectFeature (which stores off the ID on hover rather than just selecting) on the polygonLayer. It actually works just fine except the z axis of the linelayer while added is lower, so it shows the new line being drawn under the polygon. Would rather have the line show over the polygon. I know it's because when the selectFeature control is active it's setting the z-index of the polygon layer higher than the lineLayer.
I manually set the z-index of the linelayer higher than the polygon layer using lineLayer.setZIndex(800) or whatever, and that certainly makes the new line draw over the polygons, but then the selectFeature events don't trigger.
I've considered several solutions including adding the drawFeature to my polygon layer and then moving the feature to the line layer when done, but it's still rendering under the polygons, I even played with the graphicZIndex on the "temporary" style for my stylemap on the polygon layer. to no avail (i did set the renderOptions zindexing to true on the polygon layer)
I may be approaching this from the wrong angle, so I'm open to suggestions. If there was a function available on the vector layer something like getFeatureByPosition(position), I could grab the position on sketchStarted, and sketchEnded events and query that, but so far I've been unable to find anything like that.
I'm not on my dev box at the moment in case anyone is wondering why no code. Wanted to post this from work, but the base network is having issues displaying the logon page (due to ssl I think)
So my solution required a few things. First I needed to upgrade from OL 2.10 to the latest 2.13.1. Mostly this was due to needing a new event that was added to 2.11 (I think or maybe 2.12) the event is "featureover" which can be caught at the map level and therefore will trigger on all layers, so I'm not fighting with Z-Index of the select. I removed the select control from the polygon layer as it was not needed.
var featureOverHandler = function(event){
if (event.feature.layer.id == polygonLayer.id) {
selectedPolygonId = event.feature.attributes.POLYGON_ID;
console.log("Selected Polygon Id: " + selectedPolygonId);
map.events.unregister('featureover',map,featureOverHandler);
map.events.register('featureout',map,featureOutHandler);
}
};
var featureOutHandler = function (event) {
if (event.feature.layer.id == polygonLayer.id) {
selectedPolygonId = 0;
console.log("Cleared Selected Polygon ID ");
map.events.unregister('featureout', map, featureOutHandler);
map.events.register('featureover', map, featureOverHandler);
}
};
These will catch the polygon that is currently hovered over. But then I add events to catch where the line starts and ends. Since as of 2.11, they changed the way the "sketchstarted" event works, you can non longer use that to capture which polygon the pointer was over when the first point was added. I used the point callback on the vector layer.
var vDrawOptions = {
callbacks: {
"point": function (p) {
if (p.parent.components.length == 2) {
console.log("First Point added");
startingPolygonId = selectedPolygonId;
}
}
}
}
vectorAddControl = new OpenLayers.Control.DrawFeature(vectorLayer, OpenLayers.Handler.Path,vDrawOptions);
however, the "skecthcomplete" can still be used to capture the ending point (and thus polygon)
function vSketchComplete(evt) {
endingPolygonId = selectedPolygonId;
};
vectorLayer.events.register('sketchcomplete', vectorLayer, vSketchComplete);
Hopefully this will help someone else, in a similar situation.

Exclude overlaid element from Google Maps viewport bounds

I am using Google Maps API v3 to create an inline map on a website. In its container element, I also have an absolute positioned overlay which shows some detail information, visually hovering over the map. Determining on context this element may grow up to the size of the entire map element.
All this is working fine, however the Maps instance of course still considers the overlaid part of the map a valid usable part of the map. This means that, especially if the overlay is at maximum height, setCenter doesn't focus on the visible center, and routes drawn with DirectionsRenderer are partially underneath the overlay.
See this image:
Is there a way to limit the actual viewport to the blueish area, so that setCenter centers on the arrow tip and setBounds fits to the blue part?
I have managed to implement an acceptably functional workaround for the time being.
Some general notes which are good to know:
Every Map object has a Projection, which can convert between LatLng points to map points.
The map points a Projection uses for calculation are in 'world' coordinates, meaning they are pixels on the world map at zoom level 0.
Every zoom level exactly doubles the number of pixels shown. This means that the number of pixels in a given map point equals 2 ^ zoom.
The samples below assume a 300px wide sidebar on the right - adapting to other borders should be easy.
Centering
Using this knowledge, it becomes trivial to write a custom function for off-center centering:
function setCenter(latlng)
{
var z = Math.pow(2, map.getZoom());
var pnt = map.getProjection().fromLatLngToPoint(latlng);
map.setCenter(map.getProjection().fromPointToLatLng(
new google.maps.Point(pnt.x + 150/z, pnt.y)));
}
The crucial bits here are the z variable, and the pnt.x + 150/z calculation in the final line. Because of the above assumptions, this moves the point to center on 150 pixels to the left for the current zoom level, and as such compensates for the missing 300 pixels on the right sidebar.
Bounding
The bounds issue is far less trivial. The reason for this is that to offset the points correctly, you need to know the zoom level. For recentering this doesn't change, but for fitting to previously unknown bounds it nearly always will. Since Google Maps uses unknown margins itself internally when fitting to bounds, there is no reliable way to predict the required zoom level.
Thus a possible solution is to invoke a two-step rocket. First off, call fitBounds with the entire map. This should make the bounds and zoom level at least nearly correct. Then right after that, do a second call to fitBounds corrected for the sidebar.
The following sample implementation should be called with a LatLngBounds object as parameter, or no parameters to default to the current bounds.
function setBounds(bnd, cb)
{
var prj = map.getProjection();
if(!bnd) bnd = map.getBounds();
var ne = prj.fromLatLngToPoint(bnd.getNorthEast()),
sw = prj.fromLatLngToPoint(bnd.getSouthWest());
if(cb) ne.x += (300 / Math.pow(2, map.getZoom()));
else google.maps.event.addListenerOnce(map,'bounds_changed',
function(){setBounds(bnd,1)});
map.fitBounds(new google.maps.LatLngBounds(
prj.fromPointToLatLng(sw), prj.fromPointToLatLng(ne)));
}
What we do here at first is get the actual points of the bounds, and since cb isn't set we install a once-only event on bounds_changed, which is then fired after the fitBounds is completed. This means that the function is automatically called a second time, after the zoom has been corrected. The second invocation, with cb=1, then offsets the box to correct for the 300 pixel wide sidebar.
In certain cases, this can lead to a slight off-animation, but in practice I've only seen this occur when really spamclicking on buttons causing a fit operation. It's running perfectly well otherwise.
Hope this helps someone :)
You can use the map panBy() method which allows you to change the center of the map by a given distance in pixels.
Hope this helps!
I had a similar need and ended up just forcing some "padding" to the east of a LatLngBounds object.
On the upside, it's simple and it works. On the downside it's not really versatile. Just a quick little hack.
// start with a standard LatLngBounds object, extending as you usually would...
bounds = new google.maps.LatLngBounds();
// ...
ne = bounds.getNorthEast();
sw = bounds.getSouthWest();
// the multiplier used to add space; positive for east, negative for west
lngPadding = 1.5
extendedLng = ne.lng() + (ne.lng() - sw.lng()) * lngPadding;
// copy original and extend with the new Lng
extendedBounds = bounds;
extendedBounds.extend(new google.maps.LatLng(ne.lat(), extendedLng));
map.fitBounds(extendedBounds);

Google Maps API V3: getBounds() convert result for fitBounds()

NO, THE ABOVE ANSWERS DON'T ANSWER MY QUESTION. PLEASE READ MY UPDATE BELOW TO SEE THE CLARIFICATION WHY THIS IS A DIFFERENT CASE!!!
I'm using Google maps API V3. When I write this code:
map.fitBounds(map.getBounds());
the map zooms out!
I understand that it's according to the documentation since fitBounds ensures to show the given bounds on the map so that all the edges are shown inside the map. Therefore, the answer I'm looking for lies into the following:
How to modify the result of getBounds to be used for fitBounds without zoom effect afterwards?
Basically, I'm quite sure this can be calculated because that's what the map does, but in the opposite direction, when adding margins, to show the given bounds completely. In these terms, tell me how to calculate the margins (and therefore, how to calculate the correct input for fitBounds having output from getBounds) and your answer will be accepted.
Thanks in advance!
Update:
Zoom and Center retrieval and setting does not work for me! here's the reason why:
I am going to hold the map's viewport information in the database. Later on, I want to re-create the map and show the exact same location again. However, the mapping platform can differ from user to user. Therefore, zoom and center is not standard between different mapping platforms/frameworks and cannot be used in all them with the same results. Therefore, the only standard way is to hold the bounds in the database. So the question needs an answer exactly in the direction it is asking for.
Store instead of the bounds the zoom and center of the map and restore these values later.
I had the exact same problem and decided to shrink the bounding box resulting from getBounds() with 15%. That does the job nicely for me:
var neLat = map.getBounds().getNorthEast().k;
var neLong = map.getBounds().getNorthEast().B;
var swLat = map.getBounds().getSouthWest().k;
var swLong = map.getBounds().getSouthWest().B;
var boundingBoxPerc = 0.15;
var mapWidth = neLong - swLong;
var mapHeight = neLat - swLat;
var ne = new google.maps.LatLng(neLat - mapHeight * boundingBoxPerc, neLong - mapWidth * boundingBoxPerc);
var sw = new google.maps.LatLng(swLat + mapHeight * boundingBoxPerc, swLong + mapWidth * boundingBoxPerc);
map.fitBounds(new google.maps.LatLngBounds(sw, ne));

Categories

Resources