Webflow CMS + Leaflet + Filtres par distance - javascript

I am currently working on a real estate website. I use leaflet to geolocalize the properties on a map.
here is the code that displays the markers on the map
window.GeoComponents = {
maps2d: [],
init2d: function () {
// Clean up existing maps, in case this is reinitializing
window.GeoComponents.maps2d.forEach(function (map) {
map.remove();
});
window.GeoComponents.maps2d = [];
// Set up Leaflet for each [data-geo-map]
Array.from(document.querySelectorAll('[data-geo-map]')).forEach(function (mapEl) {
const mapId = mapEl.getAttribute('data-geo-map');
const map = L.map(mapEl);
window.GeoComponents.maps2d.push(map);
// Mise en place de la map
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution:
'© OpenStreetMap contributors',
}).addTo(map);
// Mise en place des markers
const allCoordinates = [];
const markers = [];
Array.from(document.querySelectorAll('[data-geo-place="' + mapId + '"]')).forEach(function (
placeEl
) {
const coordinates = [
placeEl.value.coordinates.latitude,
placeEl.value.coordinates.longitude,
];
allCoordinates.push(coordinates);
const marker = L.marker(coordinates).addTo(map).bindPopup(placeEl.innerHTML);
markers.push(marker);
// Au clic, montre le marker et sa popup sur la map
function clickPlace() {
map.panTo(coordinates, {
animate: true,
duration: 0.5,
});
// Close other popups
markers.forEach(function (otherMarker) {
otherMarker.closePopup();
});
marker.openPopup();
}
placeEl.addEventListener('click', clickPlace);
map.on('unload', function () {
placeEl.removeEventListener('click', clickPlace);
});
});
// Zoom to the markers added
map.fitBounds(L.latLngBounds(allCoordinates));
});
},
};
window.addEventListener('DOMContentLoaded', window.GeoComponents.init2d);
window.fsAttributes = window.fsAttributes || [];
window.fsAttributes.push([
'cmsfilter',
(filterInstances) => {
console.log('Filtres CMS Finsweet chargés avec succès');
// The callback passes a `filterInstances` array with all the `CMSFilters` instances on the page.
const [filterInstance] = filterInstances;
// The `renderitems` event runs whenever the list renders items after filtering.
filterInstance.listInstance.on('renderitems', (renderedItems) => {
const itemsOnScreen = Array.from(renderedItems);
if (itemsOnScreen.length === 0) {
console.log('Nouveau filtrage');
console.log('Pas de nouvelles annonces.');
const MapOnScreen = document.querySelector('.annonces_map');
// ajoute ici une fonction pour cacher la map lorsque les résultats sont vides.
} else {
console.log('Nouveau filtrage');
console.log('Plusieurs annonces.');
//Creation des latitudes/longitude selon les annonces
const places = Array.from(document.querySelectorAll('[data-geo-place]'));
places.forEach(function (el) {
const elCoords = el
.querySelector('[data-geo-coordinates]')
.textContent.split(',')
.map(Number);
const value = {
coordinates: {
latitude: elCoords[0],
longitude: elCoords[1],
},
};
el.value = value;
// Just for debug(?)
el.setAttribute('data-geo-value', JSON.stringify(value));
});
//Chargement de la Map
window.GeoComponents.init2d();
}
});
},
]);
on my Webflow CMS I apply 3 attributes to my HTLM elements
A "data-geo-map = 2" to target the div that will contain the map
A "data-geo-place = 2" for the pop-up to display on the map
A "data-geo-coordinates=1" which contains the latitude and longitude to place the markers on the map.
My problem is that I would like to create a filter that filters according to a distance in KM from a selected city. And that makes a circle around this same property.
Example if I choose the city of LA with a radius of 20km around LA I want to display markers and real estate ads that are in the circle and not outside.
Here is the link of my site in reading only or you will be able to consult all the information.
Read-link : https://preview.webflow.com/preview/api-transaction-91d626df5379af052a1accc?utm_medium=preview_link&utm_source=designer&utm_content=api-transaction-91d626df5379af052a1accc&preview=d4913aafef21e15266b06c6d2f3e1769&pageId=63d8cea19751ec322a935af2&workflow=preview
I thank you in advance for helping me bring

Related

leaflet loops to check if marker already exists in cluster (so as not to duplicate it)

I have a leaflet map which contains markers for the 10 most populous cities in a country. When a user clicks on a city marker an AJAX call is made. I pass the city lat, lng and country code to an API which returns 5 nearby airports (name, lat, lng). I then loop through the resulting JSON data to place markers for each airport on the map.
My problem is that some cities are near each other and thus a duplicate airport marker is sometimes placed on the map.
I want to prevent duplicate markers on the map. I've tried creating a new array then filtering it but I'm not able to get that working.
I'm also wondering if there is a simpler solution to this problem. Any help would be much appreciated. Relevant code below:
if (map.hasLayer(capCityCluster)) {
map.removeLayer(capCityCluster);
}
capCityCluster = new L.markerClusterGroup();
map.addLayer(capCityCluster);
var largeCityMarker = L.marker(new L.LatLng(cityLat, cityLng), ({
icon: cityIcon
})).bindPopup(`<div class="markerContainer"><h3>${cityName}</h3><img class="markerThumbnail" src='${cityThumbnailImg}' onerror="this.style.display='none'"><p class="markerTxtDescription">${cityInfo}</p><div id="city-link">${cityText}</div></div>`, cityOptions).once('click', function(e) {
map.flyTo(e.latlng, 10);
$.ajax({
url: "assets/php/airports.php",
type: 'GET',
dataType: 'json',
data: {
lat: this.getLatLng().lat,
lng: this.getLatLng().lng,
countryCodeA2: borderCountryCode,
},
success: function(result) {
//airport markers
result.data.capCityAirports.items.forEach(airport => {
var airportIcon = L.icon({
iconUrl: 'assets/img/icons/airport.png',
iconSize: [50, 50],
popupAnchor: [0, -15]
});
airportName = airport.title;
airportLat = airport.position.lat;
airportLng = airport.position.lng;
var airportMarker = L.marker(new L.LatLng(airportLat, airportLng), ({
icon: airportIcon
})).bindPopup(airportName);
capCityCluster.addLayer(airportMarker);
});
You can go through all layers in the group and check if a marker with the same latlngs exists:
var alreadyExists = false;
var latlng = new L.LatLng(airportLat, airportLng);
capCityCluster.getLayers().forEach((layer)=>{
if(!alreadyExists && layer instanceof L.Marker && layer.getLatLng().equals(latlng)){
alreadyExists = true;
}
});
// if alreadyExists is true, it is a duplicate
if(!alreadyExists){
var airportMarker = L.marker(latlng, {
icon: airportIcon
}).bindPopup(airportName);
capCityCluster.addLayer(airportMarker);
}
Also you have a mistake in your marker creation. remove the () around the options:
var airportMarker = L.marker(new L.LatLng(airportLat, airportLng), >>>(<<<{
icon: airportIcon
}>>>)<<<).bindPopup(airportName);

Get the ID of a marker on click in Mapbox

I have an angular view in which I bring data from some points from an api, I use a method to place the markers depending on the coordinates that the api gives me. In addition to the coordinates, the data has an ID.
I need that when I click on the marker in addition to showing me the information to take the ID in some way and save it in a variable to be able to perform functions with that specific point. So far I have this.
map: Mapboxgl.Map; // THE MAP
marker: Mapboxgl.Marker; // THE MARKER
// THE METHOD TO CREATE THE MARKERS ON THE MAP
creteGeoJSON(data) {
data.forEach((element) => {
const el: HTMLElement = document.createElement('div');
el.className = 'marker';
el.style.backgroundImage = 'url(../../../../assets/img/icon.png)';
el.style.width = '30px';
el.style.height = '30px';
el.style.cursor = 'pointer';
el.style.backgroundSize = 'cover';
this.marker = new Mapboxgl.Marker(el)
.setLngLat(
element.coor
.split(',')
.reverse()
.map((x) => +x)
)
.setPopup(
new Mapboxgl.Popup({ offset: 25 }) // add popups
.setHTML(
`<h2>ID: ${element.id} </h2>`
)
)
.addTo(this.map);
this.currentMarkers.push(this.marker);
});
}
// THE FUNCTION TO GET THE LAT AND LONG
getCoords(e) {
this.map.getCanvas().style.cursor = 'pointer';
this.map.on('click', (e) => {
const lat = e.lngLat.lat;
const lng = e.lngLat.lng;
// GET SOME ID
});
}
If you want to do something when the user clicks on a marker, you should add the click event to the marker, not to the map.
A marker contains an HTML element, so you can just do:
marker = new Mapboxgl.Marker(el)
//...
.addTo(map);
el.addEventListener('click', () => {
// in here you have access to the `element` object that contains your data
});

Best way to convert Leaflet Geojson layers to Leaflet rectangle vector [duplicate]

I am trying to use leaflet's edit function on polygons that I loaded from my database. When I click on leaflet's edit button I get the error
Cannot read property 'enable' of undefined
This thread describes a similar problem, and user ddproxy said
"Since FeatureGroup extends LayerGroup You can walk through the layers
presented and add them individually to the FeatureGroup used for
Leaflet.draw"
I am confused what he means by "walk through", I thought I was adding a layer group, so i'm not sure what I would be walking through. Does this have to do with the fact that i'm adding the polygons as a geoJSON object? Adding the polygons to the map, binding their popups, and assigning them custom colors works perfectly FYI.
The following is the relevant code:
<script>
window.addEventListener("load", function(event){
//other stuff
loadHazards();
});
//next 6 lines siply add map to page
var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
var osmAttrib = '© OpenStreetMap contributors'
var osm = L.tileLayer(osmUrl, { maxZoom: 18, attribution: osmAttrib})
var map = new L.Map('map', { center: new L.LatLng(39.255467, -76.711964), zoom: 16 })
osm.addTo(map);
var drawnItems = L.featureGroup().addTo(map);
var Hazards = L.featureGroup().addTo(map);
L.control.layers({
'osm': osm.addTo(map)
},
{
'drawlayer': drawnItems,
"Hazards" : Hazards,
"Tickets": Tickets
},
{
position: 'topleft', collapsed: false
}
).addTo(map);
map.addControl(new L.Control.Draw({
edit: {
featureGroup: Hazards,
poly: {
allowIntersection: false
}
},
draw: {
polygon: {
allowIntersection: false,
showArea: true
},
rectangle:false,
circle:false,
circlemarker:false
}
}));
map.on(L.Draw.Event.CREATED, function (event) {
var layer = event.layer;
drawnItems.addLayer(layer);
});
</script>
And the loadHazards() function:
function loadHazards(){
$.ajax({
type: 'GET',
url:'/loadPolygonFromDatabase',
success : function(polygons){
polygons = JSON.parse(polygons);
var toAdd = [];
for (i in polygons){
var item = {
"type" : "Feature",
"properties":{
"category":"",
"description":"",
"ID":""
},
"geometry" : {
"type":"Polygon",
"coordinates":[],
}
};
item["geometry"]["coordinates"][0] = polygons[i]["coordinates"];
item["properties"]["category"] = polygons[i]["category"];
item["properties"]["description"] = polygons[i]["description"];
item["properties"]["ID"] = polygons[i]["ID"];
toAdd.push(item);
}
//Add information to popup
var layerGroup = L.geoJSON(toAdd, {
onEachFeature: function (feature, layer) {
layer.bindPopup( '<h1>' + feature.properties.category + '</h1>'
+ '<p>' + feature.properties.description + '</p>');
layer.id = feature.properties.ID;
},
style: function(feature){
switch (feature.properties.category) {
case 'Rabid_Beavers': return {color: "#663326"};
case 'Fire': return {color: "#ff0000"};
case 'Flood': return {color: "#0000ff"};
}
}
}).addTo(Hazards);
}
});
}
Thanks in advance!
As mentioned by #ghybs Leaflet.Draw doesn't support Groups or MultiPolygons. I needed the same functionality so a few years ago I created Leaflet-Geoman (previously named leaflet.pm) which supports holes, MultiPolygons, GeoJSON and LayerGroups:
https://github.com/geoman-io/leaflet-geoman
Hope it helps.
Unfortunately Leaflet.draw plugin does not handle nested Layer Groups (same for Feature Groups / GeoJSON Layer Groups).
That is the meaning of the Leaflet.draw #398 issue you reference: they advise looping through the child layers of your Layer/Feature/GeoJSON Layer Group (e.g. with their eachLayer method). If the child layer is a non-group layer, then add it to your editable Feature Group. If it is another nested group, then loop through its own child layers again.
See the code proposed in that post:
https://gis.stackexchange.com/questions/203540/how-to-edit-an-existing-layer-using-leaflet
var geoJsonGroup = L.geoJson(myGeoJSON);
addNonGroupLayers(geoJsonGroup, drawnItems);
// Would benefit from https://github.com/Leaflet/Leaflet/issues/4461
function addNonGroupLayers(sourceLayer, targetGroup) {
if (sourceLayer instanceof L.LayerGroup) {
sourceLayer.eachLayer(function(layer) {
addNonGroupLayers(layer, targetGroup);
});
} else {
targetGroup.addLayer(sourceLayer);
}
}
In your very case, you can also refactor your code with 2 other solutions:
Instead of building your layerGroup (which is actually a Leaflet GeoJSON Layer Group) first and then add it into your Hazards Feature Group, make the latter a GeoJSON Layer Group from the beginning, and addData for each of your single Features (item):
var Hazards = L.geoJSON(null, yourOptions).addTo(map);
for (i in polygons) {
var item = {
"type" : "Feature",
// etc.
};
// toAdd.push(item);
Hazards.addData(item); // Directly add the GeoJSON Feature object
}
Instead of building a GeoJSON Feature Object (item) and parse it into a Leaflet GeoJSON Layer, you can directly build a Leaflet Polygon and add it into your Hazards Layer/Feature Group:
for (i in polygons) {
var coords = polygons[i]["coordinates"];
var style = getStyle(polygons[i]["category"]);
var popup = ""; // fill it as you wish
// Directly build a Leaflet layer instead of an intermediary GeoJSON Feature
var itemLayer = L.polygon(coords, style).bindPopup(popup);
itemLayer.id = polygons[i]["ID"];
itemLayer.addTo(Hazards);
}
function getStyle(category) {
switch (category) {
case 'Rabid_Beavers': return {color: "#663326"};
case 'Fire': return {color: "#ff0000"};
case 'Flood': return {color: "#0000ff"};
}
}

Leaflet Draw "Cannot read property 'enable' of undefined" adding control to geoJSON layer

I am trying to use leaflet's edit function on polygons that I loaded from my database. When I click on leaflet's edit button I get the error
Cannot read property 'enable' of undefined
This thread describes a similar problem, and user ddproxy said
"Since FeatureGroup extends LayerGroup You can walk through the layers
presented and add them individually to the FeatureGroup used for
Leaflet.draw"
I am confused what he means by "walk through", I thought I was adding a layer group, so i'm not sure what I would be walking through. Does this have to do with the fact that i'm adding the polygons as a geoJSON object? Adding the polygons to the map, binding their popups, and assigning them custom colors works perfectly FYI.
The following is the relevant code:
<script>
window.addEventListener("load", function(event){
//other stuff
loadHazards();
});
//next 6 lines siply add map to page
var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
var osmAttrib = '© OpenStreetMap contributors'
var osm = L.tileLayer(osmUrl, { maxZoom: 18, attribution: osmAttrib})
var map = new L.Map('map', { center: new L.LatLng(39.255467, -76.711964), zoom: 16 })
osm.addTo(map);
var drawnItems = L.featureGroup().addTo(map);
var Hazards = L.featureGroup().addTo(map);
L.control.layers({
'osm': osm.addTo(map)
},
{
'drawlayer': drawnItems,
"Hazards" : Hazards,
"Tickets": Tickets
},
{
position: 'topleft', collapsed: false
}
).addTo(map);
map.addControl(new L.Control.Draw({
edit: {
featureGroup: Hazards,
poly: {
allowIntersection: false
}
},
draw: {
polygon: {
allowIntersection: false,
showArea: true
},
rectangle:false,
circle:false,
circlemarker:false
}
}));
map.on(L.Draw.Event.CREATED, function (event) {
var layer = event.layer;
drawnItems.addLayer(layer);
});
</script>
And the loadHazards() function:
function loadHazards(){
$.ajax({
type: 'GET',
url:'/loadPolygonFromDatabase',
success : function(polygons){
polygons = JSON.parse(polygons);
var toAdd = [];
for (i in polygons){
var item = {
"type" : "Feature",
"properties":{
"category":"",
"description":"",
"ID":""
},
"geometry" : {
"type":"Polygon",
"coordinates":[],
}
};
item["geometry"]["coordinates"][0] = polygons[i]["coordinates"];
item["properties"]["category"] = polygons[i]["category"];
item["properties"]["description"] = polygons[i]["description"];
item["properties"]["ID"] = polygons[i]["ID"];
toAdd.push(item);
}
//Add information to popup
var layerGroup = L.geoJSON(toAdd, {
onEachFeature: function (feature, layer) {
layer.bindPopup( '<h1>' + feature.properties.category + '</h1>'
+ '<p>' + feature.properties.description + '</p>');
layer.id = feature.properties.ID;
},
style: function(feature){
switch (feature.properties.category) {
case 'Rabid_Beavers': return {color: "#663326"};
case 'Fire': return {color: "#ff0000"};
case 'Flood': return {color: "#0000ff"};
}
}
}).addTo(Hazards);
}
});
}
Thanks in advance!
As mentioned by #ghybs Leaflet.Draw doesn't support Groups or MultiPolygons. I needed the same functionality so a few years ago I created Leaflet-Geoman (previously named leaflet.pm) which supports holes, MultiPolygons, GeoJSON and LayerGroups:
https://github.com/geoman-io/leaflet-geoman
Hope it helps.
Unfortunately Leaflet.draw plugin does not handle nested Layer Groups (same for Feature Groups / GeoJSON Layer Groups).
That is the meaning of the Leaflet.draw #398 issue you reference: they advise looping through the child layers of your Layer/Feature/GeoJSON Layer Group (e.g. with their eachLayer method). If the child layer is a non-group layer, then add it to your editable Feature Group. If it is another nested group, then loop through its own child layers again.
See the code proposed in that post:
https://gis.stackexchange.com/questions/203540/how-to-edit-an-existing-layer-using-leaflet
var geoJsonGroup = L.geoJson(myGeoJSON);
addNonGroupLayers(geoJsonGroup, drawnItems);
// Would benefit from https://github.com/Leaflet/Leaflet/issues/4461
function addNonGroupLayers(sourceLayer, targetGroup) {
if (sourceLayer instanceof L.LayerGroup) {
sourceLayer.eachLayer(function(layer) {
addNonGroupLayers(layer, targetGroup);
});
} else {
targetGroup.addLayer(sourceLayer);
}
}
In your very case, you can also refactor your code with 2 other solutions:
Instead of building your layerGroup (which is actually a Leaflet GeoJSON Layer Group) first and then add it into your Hazards Feature Group, make the latter a GeoJSON Layer Group from the beginning, and addData for each of your single Features (item):
var Hazards = L.geoJSON(null, yourOptions).addTo(map);
for (i in polygons) {
var item = {
"type" : "Feature",
// etc.
};
// toAdd.push(item);
Hazards.addData(item); // Directly add the GeoJSON Feature object
}
Instead of building a GeoJSON Feature Object (item) and parse it into a Leaflet GeoJSON Layer, you can directly build a Leaflet Polygon and add it into your Hazards Layer/Feature Group:
for (i in polygons) {
var coords = polygons[i]["coordinates"];
var style = getStyle(polygons[i]["category"]);
var popup = ""; // fill it as you wish
// Directly build a Leaflet layer instead of an intermediary GeoJSON Feature
var itemLayer = L.polygon(coords, style).bindPopup(popup);
itemLayer.id = polygons[i]["ID"];
itemLayer.addTo(Hazards);
}
function getStyle(category) {
switch (category) {
case 'Rabid_Beavers': return {color: "#663326"};
case 'Fire': return {color: "#ff0000"};
case 'Flood': return {color: "#0000ff"};
}
}

Leaflet popups for specific base maps

so I'm making a website using leaflet with dozens of base maps. I want to incorporate information about each map that is only visible if the user wants it. To do this, I would like to make an overlay map with popups, but I want the popups to change depending on the base map selected by the user.
How would I go about doing this?
Thank You So Much
You need to either use a plugin that keeps track of the base maps for you (like active layers) or you need to do it yourself.
If you are using the Leaflet layers control, you can subscribe to the basemapchange event to do this easily.
You need two things: active base layer management (easy) and dynamic popups (not too hard)
To wit:
First, here is the event handler to track active base layer when it changes.
map.on("baselayerchange",
function(e) {
// e.name has the layer name
// e.layer has the layer reference
map.activeBaseLayer = e.layer;
console.log("base map changed to " + e.name);
});
Because using L.marker().bindPopup() creates the popup content right there and does not support callbacks, you must manually create the popups in response to click event by calling map.openPopup() with your dynamic html (dynamic because it uses a variable: the active basemap name)
marker.on("click", function(e) {
var html = "Current base layer: <br/><b>" + map.activeBaseLayer.options.name + "<b>";
map.openPopup(html,
e.latlng, {
offset: L.point(1, -24)
});
});
Here is a working example on JS fiddle: http://jsfiddle.net/4caaznsc/
Working code snippet also below (relies on Leaflet CDN):
// Create the map
var map = L.map('map').setView([39.5, -0.5], 5);
// Set up the OSM layer
var baseLayer1 = L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
name: "Base layer 1"
});
var baseLayer2 = L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
name: "Base layer 2"
});
// add some markers
function createMarker(lat, lng) {
var marker = L.marker([lat, lng]);
marker.on("click", function(e) {
var html = "Current base layer: <br/><b>" + map.activeBaseLayer.options.name + "<b>";
map.openPopup(html,
e.latlng, {
offset: L.point(1, -24)
});
});
return marker;
}
var markers = [createMarker(36.9, -2.45), createMarker(36.9, -2.659), createMarker(36.83711, -2.464459)];
// create group to hold markers, it will be added as an overlay
var overlay = L.featureGroup(markers);
// show overlay by default
overlay.addTo(map);
// show features
map.fitBounds(overlay.getBounds(), {
maxZoom: 11
});
// make up our own property for activeBaseLayer, we will keep track of this when it changes
map.activeBaseLayer = baseLayer1;
baseLayer1.addTo(map);
// create basemaps and overlays collections for the layers control
var baseMaps = {};
baseMaps[baseLayer1.options.name] = baseLayer1;
baseMaps[baseLayer2.options.name] = baseLayer2;
var overlays = {
"Overlay": overlay
};
// create layers control
var layersControl = L.control.layers(baseMaps, overlays).addTo(map);
// update active base layer when changed
map.on("baselayerchange",
function(e) {
// e.name has the name, but it may be handy to have layer reference
map.activeBaseLayer = e.layer;
map.closePopup(); // any open popups will no longer be correct; take easy way out and hide 'em
});
#map {
height: 400px;
}
<script src="https://npmcdn.com/leaflet#0.7.7/dist/leaflet.js"></script>
<link href="https://npmcdn.com/leaflet#0.7.7/dist/leaflet.css" rel="stylesheet"/>
<div id="map"></div>

Categories

Resources