I need to update leaflet map with aggregated data on zoom change, so that when user changes the zoom, aggregation is performed on the backend and the data is sent to the map.
I have a problem with displaying the data from backend, because when data is changed, map isn't updated and then if I pan, the map is updated, but the layer with markers is shifted and resized to fit the viewport, so markers are not in their actual coordinates (its better explained by the attached images).
Initial map state:
After zoom and update (map didn't detect changes):
After small pan (map detected changes, but the geoJson layer is shifted and resized):
Code for updating the map (it removes the old geoJson layer and creates the new one to draw):
refreshMap(data) {
if (this.layer) {
this.layer.clearLayers();
}
let pointToLayer = (feature, latlng) => {
let radius = 10 + Math.log2(feature.properties.count);
let circleMarker = L.circleMarker(latlng, {
radius,
fillColor: uiSettings.map.cluster.fillColor,
fillOpacity: uiSettings.map.cluster.fillOpacity,
color: uiSettings.map.cluster.strokeColor,
weight: uiSettings.map.cluster.strokeWeight,
opacity: uiSettings.map.cluster.strokeOpacity,
});
circleMarker.bindTooltip(`${feature.properties.count}`, { permanent: true });
return circleMarker;
};
this.layer = L.geoJSON(data, { pointToLayer });
this.layer.addTo(this.map);
}
I wasn't able to google similar cases, so maybe somebody had the problem with this or can say what can cause it?
Related
I am working with a WordPress/jQuery/Google Maps setup to display a map of listings to users.
The jQuery.goMap.map code is used to load in the Google Maps instance used on the WordPress plugin.
I have wrote the following functions to store and load the latitude, longitude, and zoom level with local storage. The storing of the lat/lng and zoom levels is working, and the loading of the zoom level is working, but I cannot get the map to center on the loaded latitude and longitude position.
I have tried using bounds.extend(latLng); and jQuery.goMap.map.fitBounds(bounds); in the loadUserZoom function, but the result is a fully zoomed in map. This means the stored zoom level value is being ignored.
The current functioning code can be tested here.
The Clear text link in the header navigation can be used to clear the local storage values from the browser. This is implemented for testing purposes.
Any assistance is greatly appreciated.
Function: storeUserZoom
function storeUserZoom() {
let zoom = jQuery.goMap.map.getZoom();
localStorage.setItem( 'zoom', zoom);
let center = jQuery.goMap.map.getCenter();
let lat = center.lat();
let lng = center.lng();
let latLng = {
lat: lat,
lng: lng
}
localStorage.setItem( 'latLng', JSON.stringify(latLng));
}
Function: loadUserZoom
function loadUserZoom() {
if (localStorage.getItem( 'zoom' )) {
let zoom = parseInt(localStorage.getItem( 'zoom' ));
console.log(zoom);
// Logs correct zoom level
let latLng = JSON.parse(localStorage.getItem( 'latLng' ));
console.log(latLng);
// Logs Object { lat: 51.69124213478852, lng: -113.2478200914128 }
jQuery.goMap.map.setZoom(zoom);
jQuery.goMap.map.setCenter(latLng);
// latLng used is incorrect
}
}
I believe I have zeroed in on the problem by adjusting how the loadUserZoom function was executed in the initMap function.
The loadUserZoom function was wrapped in a Google Maps listen once event listener when the map was idle. The code is included below.
google.maps.event.addListenerOnce(jQuery.goMap.map, 'idle', function() {
loadUserZoom();
});
I had it set initially to addListener, which seemed to conflict with the required functionality. I assume this meant it was execute regularly whenever the map was in an idle state.
My updated loadUserZoom function is included below.
function loadUserZoom() {
if (localStorage.getItem( 'zoom' )) {
let zoom = parseInt(localStorage.getItem( 'zoom' ));
let latLng = JSON.parse(localStorage.getItem( 'latLng' ));
jQuery.goMap.map.setZoom(zoom);
jQuery.goMap.map.setCenter(latLng);
}
}
Background:
I'm working on integrating HERE maps with trucks restrictions to our web-based software solution.
Issue:
Halfway through the integration, I've noticed that when zooming-in/zooming-out the labels and truck restriction signs are blinking after reload. I've captured same behaviour in the example provided by HERE (link) themselves.
As the issue occurs in their own examples I think it is safe to state that it's not my implementation problem.
I've seen (also in their examples), that when their vector tiles are integrated using mapbox.gl, the zoom-in/zoom-out becomes smooth, but I would prefer to use their own javascript library due to other advantages (like traffic incidents display, etc.).
Question:
Anyone has any ideas how could that be solved?
I tried to reproduce the effect and i understand what you mean. From what i understand (i have not worked with HERE maps before), while a user zoom in/out the re-rendering process is the one that produces that single-flicker of a point sometimes.
There are 2 JSFiddle examples A and B, you will spot a difference but i think the problem not eliminated.
Example Code
/**
* A full list of available request parameters can be found in the Routing API documentation.
* see: http://developer.here.com/rest-apis/documentation/routing/topics/resource-calculate-route.html
*/
var routeRequestParams = {
routingMode: 'fastest',
transportMode: 'truck',
trafficMode: 'enabled',
origin: '40.7249546323,-74.0110042', // Manhattan
destination: '40.7324386599,-74.0341396'
},
routes = new H.map.Group();
async function calculateRoutes(platform) {
var router = await platform.getRoutingService(null, 8);
// The blue route showing a simple truck route
calculateRoute(router, routeRequestParams, {
strokeColor: 'rgba(0, 128, 255, 0.7)',
lineWidth: 10
});
// The green route showing a truck route with a trailer
calculateRoute(router, Object.assign(routeRequestParams, {
'truck[axleCount]': 4,
}), {
strokeColor: 'rgba(25, 150, 10, 0.7)',
lineWidth: 7
});
// The violet route showing a truck route with a trailer
calculateRoute(router, Object.assign(routeRequestParams, {
'truck[axleCount]': 5,
'truck[shippedHazardousGoods]': 'flammable'
}), {
strokeColor: 'rgba(255, 0, 255, 0.7)',
lineWidth: 5
});
}
/**
* Calculates and displays a route.
* #param {Object} params The Routing API request parameters
* #param {H.service.RoutingService} router The service stub for requesting the Routing API
* #param {mapsjs.map.SpatialStyle.Options} style The style of the route to display on the map
*/
async function calculateRoute (router, params, style) {
await router.calculateRoute(params, async function(result) {
await addRouteShapeToMap(style, result.routes[0]);
}, console.error);
}
/**
* Boilerplate map initialization code starts below:
*/
// set up containers for the map + panel
var mapContainer = document.getElementById('map');
// Step 1: initialize communication with the platform
// In your own code, replace variable window.apikey with your own apikey
var platform = new H.service.Platform({
apikey: window.apikey
});
var defaultLayers = platform.createDefaultLayers();
// Step 2: initialize a map - this map is centered over Berlin
var map = new H.Map(mapContainer,
// Set truck restriction layer as a base map
defaultLayers.vector.normal.truck,{
center: {lat: 40.745390, lng: -74.022917},
zoom: 13.2,
pixelRatio: window.devicePixelRatio || 1
});
// add a resize listener to make sure that the map occupies the whole container
window.addEventListener('resize', async () => await map.getViewPort().resize());
// Step 3: make the map interactive
// MapEvents enables the event system
// Behavior implements default interactions for pan/zoom (also on mobile touch environments)
var behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
map.addObject(routes);
/**
* Creates a H.map.Polyline from the shape of the route and adds it to the map.
* #param {Object} route A route as received from the H.service.RoutingService
*/
function addRouteShapeToMap(style, route){
route.sections.forEach(async (section) => {
// decode LineString from the flexible polyline
let linestring = await H.geo.LineString.fromFlexiblePolyline(section.polyline);
// Create a polyline to display the route:
let polyline = await new H.map.Polyline(linestring, {
style: style
});
// Add the polyline to the map
await routes.addObject(polyline);
// And zoom to its bounding rectangle
await map.getViewModel().setLookAtData({
bounds: routes.getBoundingBox()
});
});
}
// Now use the map as required...
calculateRoutes (platform);
Unfortunately there isn't a good solution for this. It is caused by the way that the page is rendered through the here api. I don't think its fixable.
Having said that you might be able to add some css that would fade in the elements so the transition is smoother.
So you would need to detect the current value of
zoom:
and if it changes you would want to trigger the css animation.
It will take a bit of work and won't be a perfect solution but its better than nothing.
I have:
const map = L.map("mapid", {preferCanvas: true});
//....
const circle = L.circle([47.6652642, -122.3161248], {
color: '#ea647f',
fillOpacity: 0.4,
radius: 30
}).addTo(map);
but calling getBounds() on circle fails:
const bounds = circle.getBounds();
It fails inside function getBounds in Circle.js which is Leaflet code,
The Leaflet getBounds method code is:
getBounds: function () {
var half = [this._radius, this._radiusY || this._radius];
return new LatLngBounds(
this._map.layerPointToLatLng(this._point.subtract(half)),
this._map.layerPointToLatLng(this._point.add(half)));
}
Trying to access this._map.layerPointToLatLng fails
I get the error that this._map is undefined
Any ideas?
===================================================
Please Note: I also have a polygon defined,
and calling the getBounds() on the polygon passes fine and works correctly, shows correctly on the map.
=> It is only the Circle.getBounds() that fails
Add center and zoom to the map.
const map = L.map("map", {center:[47.6652642, -122.3161248], zoom: 16 ,preferCanvas: true});
You need to initialise the map state with setView() or some other mechanism. The layerPointToLatLng calls will fail otherwise.
I need to color countries on a map depending on a some value (number of content items for a country). If that value is 0 (null), the respective country should not be coloured/displayed.
In terms of Leaflet.js, this means, I have a GeoJSON file containing a feature for each country of the world. However, the feature is only rendered (added to map) if the number of content items is greater than 0. When using GeoJSON input file, this works already similar to the answer to Leaflet.js: is it possible to filter geoJSON features by property?
This is the snippet for GeoJSON:
var mapLayer = L.geoJson(world, {
filter: function(feature, layer) {
return getContentItems(feature.properties.ISO2, "count");
},
onEachFeature: function (feature, layer) {
var contentCount = getContentItems(feature.properties.ISO2, "count");
if (contentCount) {
layer.setStyle({
'weight': 1,
'fillOpacity': .75,
'fillColor': "#0077c8", // Actually a function depending on contentCount
'opacity': .75,
'color': "#0077c8"
});
}
}
});
Now, the GeoJSON file is a whopping 11 MB large due to details on the map. I learned about TopoJSON, which is pretty awesome since the source file is now less than 2 MB with the same grade of detail. I also managed to get the TopoJSON data to be displayed on the map, however, I can't figure out, how to apply the filter.
Here's my current snippet for adding the TopoJSON layer:
L.TopoJSON = L.GeoJSON.extend({
addData: function(jsonData) {
if (jsonData.type === "Topology") {
for (key in jsonData.objects) {
geojson = topojson.feature(jsonData, jsonData.objects[key]);
L.GeoJSON.prototype.addData.call(this, geojson);
}
}
else {
L.GeoJSON.prototype.addData.call(this, jsonData);
}
}
});
var topoLayer = new L.TopoJSON();
$.getJSON('scripts/world.topo.json')
.done(addTopoData);
function addTopoData(topoData) {
topoLayer.addData(topoData);
topoLayer.addTo(map);
topoLayer.eachLayer(handleLayer);
}
function handleLayer(layer) {
layer.setStyle({'opacity': 0}); // Too late.
}
I tried to add the filter function to the GeoJSON extension within the TopoJSON declaration without luck. The handleLayer() function is to late, I think, the features have already been added.
EDIT:
I am able to remove a layer, if the content count is 0 by changing the handleLayer() function to
function handleLayer(layer) {
var contentCount = getContentItems(layer.feature.properties.ISO2, "count");
if (contentCount == 0) {
map.removeLayer(layer);
}
}
For performance purposes, I would however like to filter the features before being drawn. I am just stuck now where the filter function needs to be added.
After some deep Javascript reading about inheritance just as suggested by FranceImage, I found the solution I was looking for myself. Instead of just creating a new TopoJSON object with var topoLayer = new L.TopoJSON();, I am able to pass the custom filter functions as options:
var topoLayer = new L.TopoJSON(null, {
filter: function(feature, layer) {
return getNN(feature.properties.ISO2, "check");
},
onEachFeature: function (feature, layer) {
var contentCount = getContentItems(feature.properties.ISO2, "count");
if (contentCount) {
layer.setStyle({
'weight': 1,
'fillOpacity': .75,
'fillColor': "#0077c8",
'opacity': .75,
'color': "#0077c8"
});
}
}
});
No need to remove layers with the handleLayers function.
I am working with leaflet api where user can put markers on map. I have made a custom button for putting marker.
I am willing to draw line between those markers i.e. using
L.polylines() but as I am new to javascript and leaflet I can't
understand how to pass those latlng point to array which will later be
used in these functions. For initial working I have passed static
coordinates(working as req).
L.easyButton('fa-link', function () {
var secureThisArea = [[-81, 100.75], [-76.50, 245.75], [-145.50, 184.25], [-128, 311.75]];
map.on('click', function fencePlace(e) {
L.marker([-81, 100.75], { icon: fenceIcon, draggable: true }).bindPopup("this is first").addTo(map);
L.marker([-76.50, 245.75], { icon: fenceIcon, draggable: true }).bindPopup("this is second").addTo(map);
L.marker([-145.50, 184.25], { icon: fenceIcon, draggable: true }).bindPopup("this is third").addTo(map);
L.marker([-128, 311.75], { icon: fenceIcon, draggable: true }).bindPopup("this is fourth").addTo(map);
L.polyline(secureThisArea).addTo(map);
});
}).addTo(map);
Adding another value to an array is easy, e.g.:
secureThisArea.push([-81, 100.75]);
You can find more details (also about anything else JavaScript related) at Mozilla Developer Network.
If you want to use the coordinates from the marker objects, you can get those with:
var myMarker = L.marker([-81, 100.75], { icon: fenceIcon, draggable: true }),
latLng = null;
latLng = myMarker.getLatLng();
Also take a look at the Leaflet documentation.
If i understand you correctly you want to create markers on click and connect them via polylines. That's easy to do, in code with comments to explain:
// Create new empty polyline and add it to the map
var polyline = new L.Polyline([]).addTo(map);
// Handle map click event
map.on('click', function(event) {
// New marker on coordinate, add it to the map
new L.Marker(event.latlng).addTo(map);
// Add coordinate to the polyline
polyline.addLatLng(event.latlng);
});
Now afterwards if you want to get all the coordinates added to the polyline you can use the getLatLngs method of L.Polyline which returns an array of L.LatLng objects.
Reference: http://leafletjs.com/reference.html#polyline
Example: http://plnkr.co/edit/h7aMwc?p=preview