Related
I need to group waypoints based on their proximity to each other. We have resources that are assigned appointments (patients). For each patient, we see them 2x per week. I need to calculate the schedule for each day and then the route. The route part is easy with a call to the Bing Maps API. But, I'm struggling with how to generate a schedule.
For instance, if Resource1 sees 8 patients per week and each of them 2x - that's 16 appointments. Let's also assume I will see them on Mon & Wed or Tues & Thur. How would I assign which of the 8 patients should be seen on Mon/Wed & which ones are Tues/Thur. It should be based on their proximity to each other. So, give me a calculation which calculates which 4 should be seen on Mon/Wed & which 4 should be seen on Tues/Thur (assuming it's split 4/4 and not 5/3, etc)
There are a lot of ways to do this.
If you are using Power automate, there is a chance you are using Dynamics. Dynamics has a scheduler capability built in for this specific scenario. https://learn.microsoft.com/en-us/dynamics365/field-service/universal-resource-scheduling-for-field-service
You can cluster that data points initially. There are different methods to do this; distance based, travel time based, k-means clustering, DBscan clustering. distance, k-means, and DBscan are fairly common, but don't take into consideration the physical layout which can have a big impact (person might have to go out of way to get to a location on other side of a river). That said, travel time-based clustering has a much higher cost.
I want to say that the Route itinerary optimizations service would help here, but it would need you to already know which customers you are visiting for the day. https://learn.microsoft.com/en-us/bingmaps/rest-services/routes/optimized-itinerary You might be able to trick it and double the time of a shift, and then split those out afterwards. This assumes you don't care which resource visits which patients.
There are many existing services out there that provide this capability, so also worth comparing the dev/maintenance cost with a license for something already working at scale.
Looking at your scenario it looks like it would be fair to say that you would want half the patients to be visited on Mon/Wed, and the other half on Tues/Thurs. Furthermore, you would want a single resource works 4 days, and would be assigned half their patients per combination of days. With the above in mind, if we cluster the data into groups of no more than 4 patients per cluster, a single cluster would be the schedule for a single resource for a single day combo (e.g. Mon/Wed). Once you have these clusters, you can then slip them in half and assign to your resources. For a fairly accurate approach, I would want to use travel time for the clustering and would tackle this like so:
Take first patient, calculate a travel time matrix to all other patients.
Loop through all other patients and find the 3 closest and consider that an assigned cluster. Optionally assign the day combo/resource.
Take the next unclustered/unassigned patient and calculate a travel time matrix to all other patients.
Loop through all other unclustered/unassigned patients and find the 3 closest. Assign them. When half the patients are assigned, use the second day combo and allow all resources to be assigned a second time for the remaining calculated clusters.
Repeat steps 3 and 4 until all patients are assigned.
A cheaper, but less spatially accurate version of the above (less time efficient), would be to calculate straight line distances rather than travel time. Straight line distances are a simple calculation (Haversine formula).
If you want to go a step further, calculate the distance from the cluster or one of the patients in the cluster to each resource, and assign the resource who is closest (assuming the resources are not all located at the same location).
Here is proof of concept:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<style>
table, td, th {
border: 1px solid;
}
table {
width: 100%;
border-collapse: collapse;
}
</style>
</head>
<body>
<input type="button" onclick="schedulePatients()" value="Schedule patients"/>
<br/><br />
<div id="output"></div>
<script>
var resources = {
"type": "FeatureCollection",
"features": [
{ "type": "Feature", "id": 248, "properties": { "assignedMonWed": false, "assignedTuesThurs": false, "MonWebSchedule": [], "TuesThursSchedule": [] }, "geometry": { "type": "Point", "coordinates": [-122.050234696603, 47.6808627484536] } },
{ "type": "Feature", "id": 467, "properties": { "assignedMonWed": false, "assignedTuesThurs": false, "MonWebSchedule": [], "TuesThursSchedule": [] }, "geometry": { "type": "Point", "coordinates": [-122.003917771292, 47.697428787792] } },
{ "type": "Feature", "id": 415, "properties": { "assignedMonWed": false, "assignedTuesThurs": false, "MonWebSchedule": [], "TuesThursSchedule": [] }, "geometry": { "type": "Point", "coordinates": [-122.023083481977, 47.6673715172954] } },
{ "type": "Feature", "id": 564, "properties": { "assignedMonWed": false, "assignedTuesThurs": false, "MonWebSchedule": [], "TuesThursSchedule": [] }, "geometry": { "type": "Point", "coordinates": [-121.985338674036, 47.7022520412864] } },
{ "type": "Feature", "id": 533, "properties": { "assignedMonWed": false, "assignedTuesThurs": false, "MonWebSchedule": [], "TuesThursSchedule": [] }, "geometry": { "type": "Point", "coordinates": [-122.006075987628, 47.7046529915799] } }
]
};
var patients = {
"type": "FeatureCollection",
"features": [
{ "type": "Feature", "id": 1, "properties": {}, "geometry": { "type": "Point", "coordinates": [-121.997913065484, 47.7195839450159] } },
{ "type": "Feature", "id": 2, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.040368485372, 47.6869829619951] } },
{ "type": "Feature", "id": 3, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.048824870578, 47.7013191572943] } },
{ "type": "Feature", "id": 4, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.071031688833, 47.7449665072182] } },
{ "type": "Feature", "id": 5, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.015643772116, 47.7570578745646] } },
{ "type": "Feature", "id": 6, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.034052201107, 47.7451844538795] } },
{ "type": "Feature", "id": 7, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.032132503429, 47.714347009898] } },
{ "type": "Feature", "id": 8, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.04547396506, 47.6893615399414] } },
{ "type": "Feature", "id": 9, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.068392436109, 47.6832920975517] } },
{ "type": "Feature", "id": 10, "properties": {}, "geometry": { "type": "Point", "coordinates": [-121.984992007383, 47.6735499724146] } },
{ "type": "Feature", "id": 11, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.036957923548, 47.7530782996456] } },
{ "type": "Feature", "id": 12, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.066388808463, 47.7044949126107] } },
{ "type": "Feature", "id": 13, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.065430673396, 47.6936482443828] } },
{ "type": "Feature", "id": 14, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.005818300822, 47.6841991471929] } },
{ "type": "Feature", "id": 15, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.013423871487, 47.7267318710591] } },
{ "type": "Feature", "id": 16, "properties": {}, "geometry": { "type": "Point", "coordinates": [-121.991867919251, 47.6730825754312] } },
{ "type": "Feature", "id": 17, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.075035596741, 47.6825165944555] } },
{ "type": "Feature", "id": 18, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.02704005476, 47.7359799244539] } },
{ "type": "Feature", "id": 19, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.069711259786, 47.7582015474685] } },
{ "type": "Feature", "id": 20, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.00363876449, 47.696504598779] } },
{ "type": "Feature", "id": 21, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.027230559197, 47.7463213935986] } },
{ "type": "Feature", "id": 22, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.020278138182, 47.7565298732157] } },
{ "type": "Feature", "id": 23, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.041675234009, 47.7605001277431] } },
{ "type": "Feature", "id": 24, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.059226260036, 47.6842719244522] } },
{ "type": "Feature", "id": 25, "properties": {}, "geometry": { "type": "Point", "coordinates": [-121.99079261183, 47.6745521677151] } },
{ "type": "Feature", "id": 26, "properties": {}, "geometry": { "type": "Point", "coordinates": [-121.998635617356, 47.741838009627] } },
{ "type": "Feature", "id": 27, "properties": {}, "geometry": { "type": "Point", "coordinates": [-121.983315341491, 47.7529779792852] } },
{ "type": "Feature", "id": 28, "properties": {}, "geometry": { "type": "Point", "coordinates": [-121.996663951226, 47.6982171986915] } },
{ "type": "Feature", "id": 29, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.069967005873, 47.7499047491259] } },
{ "type": "Feature", "id": 30, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.032190749586, 47.757600296338] } },
{ "type": "Feature", "id": 31, "properties": {}, "geometry": { "type": "Point", "coordinates": [-121.992400954596, 47.6827035465803] } },
{ "type": "Feature", "id": 32, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.063594482899, 47.7247082989927] } },
{ "type": "Feature", "id": 33, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.075535378044, 47.6772273648534] } },
{ "type": "Feature", "id": 34, "properties": {}, "geometry": { "type": "Point", "coordinates": [-121.994743747063, 47.7387867524387] } },
{ "type": "Feature", "id": 35, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.041914349392, 47.6784018054591] } },
{ "type": "Feature", "id": 36, "properties": {}, "geometry": { "type": "Point", "coordinates": [-121.985731516363, 47.7173223782956] } },
{ "type": "Feature", "id": 37, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.066638385437, 47.7276733135571] } },
{ "type": "Feature", "id": 38, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.004119609955, 47.6871862520471] } },
{ "type": "Feature", "id": 39, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.042819720217, 47.699794194686] } },
{ "type": "Feature", "id": 40, "properties": {}, "geometry": { "type": "Point", "coordinates": [-122.029705426136, 47.72521276633] } }
]
};
//Check to see if there is enough resources for the number of patients (Half the patients assigned per day combo, with a resource having no more than 4 patients per day.
if (Math.ceil(patients.features.length) / 2 / 4 > resources.features.length) {
console.log('Not enough resources to see all patients');
}
function schedulePatients() {
var currentDayCombo = 'MonWeb';
var midPoint = Math.ceil(patients.features.length / 2);
var assigned = 0;
for (var i = 0; i < patients.features.length; i++) {
var p = patients.features[i];
var cluster = getThreeClosest(i);
//Add patient to the cluster.
cluster.push(p);
if (assigned >= midPoint) {
currentDayCombo = 'TuesThurs';
}
//Loop through resources and assign clusters.
var resource = getClosestUnassignedResource(cluster, currentDayCombo);
if (resource === null) {
//No available resource left. Handle this some how. Possibly throw an alert or log to console.
cluster.forEach(c => {
console.log(`No resources left, patient ${c.id} unassigned.`)
});
} else {
assigned += cluster.length;
}
}
//Scheduling complete. Recommend using route optimizate for the patients of each resource to get the most efficient order to visit those patients.
//Creating a table of the schedule.
var html = ['<table><tr><td>Resource ID</td><td>Mon/Wed Patient IDs</td><td>Tues/Thurs Patient IDs</td></tr>'];
resources.features.forEach(r => {
html.push(`<tr><td>${r.id}</td><td>${r.properties.MonWebSchedule.join(',')}</td><td>${r.properties.TuesThursSchedule.join(',') }</td></tr>`);
});
html.push('</table>');
document.getElementById('output').innerHTML = html.join('');
}
function getThreeClosest(patientIdx) {
//Create an array of distances and indices for each patient.
var distances = [];
var p = patients.features[patientIdx];
//Look at remaining unassigned patients and calculate distances.
for (var k = patientIdx + 1; k < patients.features.length; k++) {
var p2 = patients.features[k];
if (!p2.properties.assigned) {
distances.push({
distance: haversine(p.geometry.coordinates, p2.geometry.coordinates),
idx: k //Capture the index of the patient to do a quick lookup later.
});
}
}
//Sort the distances in ascending order.
distances.sort((a, b) => { return a.distance - b.distance });
//Possible there are less than 3 patients remaining.
var grab = Math.min(3, distances.length);
var closest = [];
for (var i = 0; i < grab; i++) {
closest.push(patients.features[distances[i].idx]);
}
return closest;
}
function getClosestUnassignedResource(cluster, dayCombo) {
//Calculate the average position of the cluster.
var sumLon = cluster.reduce((acc, curr) => acc + curr.geometry.coordinates[0], 0);
var sumLat = cluster.reduce((acc, curr) => acc + curr.geometry.coordinates[1], 0);
var origin = [sumLon / cluster.length, sumLat / cluster.length];
var currentDayProp = (dayCombo === 'MonWeb') ? 'assignedMonWed' : 'assignedTuesThurs';
//Create an array of distances and indices for each resource.
var closest = null;
var minDist = Infinity;
//Loop through resources and assign clusters.
for (var j = 0; j < resources.features.length; j++) {
var r = resources.features[j];
//Check that resource is not assigned.
if (!r.properties[currentDayProp]) {
var d = haversine(origin, r.geometry.coordinates);
if (d < minDist) {
minDist = d;
closest = r;
}
}
}
//Assign resource.
if (closest) {
closest.properties[currentDayProp] = true;
cluster.forEach(c => {
c.properties.dayCombo = dayCombo;
c.properties.resource = closest.id;
closest.properties[dayCombo + 'Schedule'].push(c.id);
});
}
return closest;
}
function haversine(p1, p2) {
var lat1 = p1[1] / 180.0 * Math.PI;
var lon1 = p1[0] / 180.0 * Math.PI;
var lat2 = p2[1] / 180.0 * Math.PI;
var lon2 = p2[0] / 180.0 * Math.PI;
var R = 6372.8; // km
var dLat = lat2 - lat1;
var dLon = lon2 - lon1;
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
var c = 2 * Math.asin(Math.sqrt(a));
return R * c;
}
</script>
</body>
</html>
I have a heat map in Qgis and with the qgis2web plugin I generated the preview page. However, it showed nothing on the page. I did some research, did some tests, but I'm not able to generate a leaflet heat map with data from a geoJson file. Can someone help me?
Follows the generated code.
Thanks.
Follows model of the file with the data:
var json_dados_centroids = {
"type": "FeatureCollection",
"name": "dados",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "VINC": "6" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ -46.489342251452101, -23.66511439561954 ] ] } },
{ "type": "Feature", "properties": { "VINC": "1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ -46.489439968502133, -23.665105357310239 ] ] } },
{ "type": "Feature", "properties": { "VINC": "0" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ -46.489537955929293, -23.665096808547311 ] ] } },
{ "type": "Feature", "properties": { "VINC": "0" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ -46.559119794597237, -23.653810429167063 ] ] } },
{ "type": "Feature", "properties": { "VINC": "0" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ -46.489140609582904, -23.665134091460494 ] ] } }
]
}
var centroids_hm = geoJson2heat(json_dados_centroids, 'VINC');
var layer_centroids = new L.heatLayer(centroids_hm, {
attribution: '',
interactive: true,
radius: 30,
max: 11917,
minOpacity: 1,
gradient: {0: '#fcfdbf', 0.019608: '#fcf4b6', 0.039216: '#fdebac', 0.0588942: '#fde2a3', 0.078431: '#fed89a',
0.098039: '#fecf92', 0.117647: '#fec68a', 0.137255: '#febd82', 0.156863: '#feb47b',
0.176471: '#feaa74', 0.196078: '#fea16e', 0.215686: '#fd9869', 0.235294: '#fc8e64',
0.254902: '#fb8560', 0.27451: '#f97b5d', 0.294118: '#f7725c', 0.313725: '#f4695c',
0.333333: '#f1605d', 0.352941: '#ec5860', 0.372549: '#e75263', 0.392157: '#e04c67',
0.411765: '#d9466b', 0.431373: '#d2426f', 0.45098: '#ca3e72', 0.470588: '#c23b75',
0.490196: '#ba3878', 0.509804: '#b2357b', 0.529412: '#aa337d', 0.54902: '#a1307e',
0.568627: '#992d80', 0.588235: '#912b81', 0.607843: '#892881', 0.627451: '#812581',
0.647059: '#792282', 0.666667: '#721f81', 0.686275: '#6a1c81', 0.705882: '#621980',
0.72549: '#5a167e', 0.745098: '#52137c', 0.764706: '#4a1079', 0.784314: '#420f75',
0.803922: '#390f6e', 0.823529: '#311165', 0.843137: '#29115a', 0.862745: '#21114e',
0.882353: '#1a1042', 0.901961: '#140e36', 0.921569: '#0e0b2b', 0.941176: '#090720',
0.960784: '#050416', 0.980392: '#02020b', 1: '#000004'}
}
);
layer_centroids.setData(centroids_hm);
function geoJson2heat(geojson, weight) {
return geojson.features.map(function(feature) {
return [
feature.geometry.coordinates[1],
feature.geometry.coordinates[0],
feature.properties[weight]
];
});
}
map.addLayer(layer_centroids);
First, your GeoJSON contains geometry in the form of multipoints. However, in 'geoJson2heat' you parse points.
I would advise converting the geometry to point (in the 'json_dados_centroids' variable).
Also layer_centroids.setData(centroids_hm) needs to be removed.
Something like that
var json_dados_centroids = {
"type": "FeatureCollection",
"name": "dados",
"crs": {
"type": "name",
"properties": {
"name": "urn:ogc:def:crs:OGC:1.3:CRS84"
}
},
"features": [{
"type": "Feature",
"properties": {
"VINC": "6"
},
"geometry": {
"type": "Point",
"coordinates": [-46.489342251452101,
-23.66511439561954
]
}
},
{
"type": "Feature",
"properties": {
"VINC": "1"
},
"geometry": {
"type": "Point",
"coordinates": [-46.489439968502133,
-23.665105357310239
]
}
},
{
"type": "Feature",
"properties": {
"VINC": "0"
},
"geometry": {
"type": "Point",
"coordinates": [-46.489537955929293,
-23.665096808547311
]
}
},
{
"type": "Feature",
"properties": {
"VINC": "0"
},
"geometry": {
"type": "Point",
"coordinates": [-46.559119794597237,
-23.653810429167063
]
}
},
{
"type": "Feature",
"properties": {
"VINC": "0"
},
"geometry": {
"type": "Point",
"coordinates": [-46.489140609582904,
-23.665134091460494
]
}
}
]
}
var centroids_hm = geoJson2heat(json_dados_centroids, 'VINC');
var layer_centroids = new L.heatLayer(centroids_hm, {
attribution: '',
interactive: true,
radius: 30,
max: 11917,
minOpacity: 1,
gradient: {
0: '#fcfdbf',
0.019608: '#fcf4b6',
0.039216: '#fdebac',
0.0588942: '#fde2a3',
0.078431: '#fed89a',
0.098039: '#fecf92',
0.117647: '#fec68a',
0.137255: '#febd82',
0.156863: '#feb47b',
0.176471: '#feaa74',
0.196078: '#fea16e',
0.215686: '#fd9869',
0.235294: '#fc8e64',
0.254902: '#fb8560',
0.27451: '#f97b5d',
0.294118: '#f7725c',
0.313725: '#f4695c',
0.333333: '#f1605d',
0.352941: '#ec5860',
0.372549: '#e75263',
0.392157: '#e04c67',
0.411765: '#d9466b',
0.431373: '#d2426f',
0.45098: '#ca3e72',
0.470588: '#c23b75',
0.490196: '#ba3878',
0.509804: '#b2357b',
0.529412: '#aa337d',
0.54902: '#a1307e',
0.568627: '#992d80',
0.588235: '#912b81',
0.607843: '#892881',
0.627451: '#812581',
0.647059: '#792282',
0.666667: '#721f81',
0.686275: '#6a1c81',
0.705882: '#621980',
0.72549: '#5a167e',
0.745098: '#52137c',
0.764706: '#4a1079',
0.784314: '#420f75',
0.803922: '#390f6e',
0.823529: '#311165',
0.843137: '#29115a',
0.862745: '#21114e',
0.882353: '#1a1042',
0.901961: '#140e36',
0.921569: '#0e0b2b',
0.941176: '#090720',
0.960784: '#050416',
0.980392: '#02020b',
1: '#000004'
}
});
function geoJson2heat(geojson, weight) {
return geojson.features.map(function(feature) {
return [
feature.geometry.coordinates[1],
feature.geometry.coordinates[0],
feature.properties[weight]
];
});
}
map.addLayer(layer_centroids);
P.S. "VINC": "6" - strange value of intensity.
I want to paint bigger circles when the data property--total is bigger.
deviceGeojson is the data, I think I should write some code within 'circle-raduis' but I dont know how to get it.
var deviceGeojson = [{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [144.961027, -37.795947]
},
"properties": {
"PC": 100,
"Mobile": 200,
"Laptop": 300,
"total": 600
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [144.960205, -37.797596]
},
"properties": {
"PC": 100,
"Mobile": 200,
"Laptop": 300,
"total": 600
}
}
];
map.addLayer({
'id': 'test',
'type': 'circle',
'source' : {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features": deviceGeojson,
}
},
'paint':{
'circle-color': '#00b7bf',
'circle-radius': {
},
'circle-stroke-width': 1,
'circle-stroke-color': '#333',
}
});
I dont know how to write the code within 'paint',Thank you
You can use expressions to get value of a property:
"circle-radius": ['get', 'total']
Or if you were to device the value of total property by say 20, you'd do something like this:
"circle-radius": ['/', ['get', 'total'], 20],
You can read more about expressions here: https://docs.mapbox.com/mapbox-gl-js/style-spec/#expressions
I'm loading a GeoJSON file of aircraft tracks into Leaflet. I'd like to plot current locations as icons and previous 'tracks' as lines. Showing all the tracks gets busy as aircraft increase, so I'd like to be able to toggle them on only when the aircraft is clicked on.
Can/how should I show/hide the LineString separately from the marker dynamically?
I've found suggestions to set the style transparent but .setStyle applies to the Feature, not the geometry.
Summarised GeoJSON:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"reg": "G-CGWP",
"type": "fixed",
"model": "website.profile"
},
"id": 12,
"geometry": {
"type": "GeometryCollection",
"geometries": [
{
"type": "LineString",
"coordinates": [
[
-0.319281196282617,
52.08664390758181
],
[
-1.076445537370006,
52.79518475653341
],
[
-0.098191354875297,
51.94810149137197
],
[
-0.940941846648286,
53.508162348603435
]
]
},
{
"type": "Point",
"coordinates": [
-0.940941846648286,
53.508162348603435
]
}
]
}
},
...
]
}
I do have control of the GeoJSON so can change that.
Since LineString collection is represented as GeometryCollection you could consider to flatten GeometryCollection into LineString geometries, (for example via Turf.js flatten function):
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "GeometryCollection",
"geometries": [
{
"type": "LineString",
"coordinates": [
[-105.00341892242432, 39.75383843460583],
[-105.0008225440979, 39.751891803969535]
]
},
{
"type": "LineString",
"coordinates": [
[-105.0008225440979, 39.751891803969535],
[-104.99820470809937, 39.74979664004068]
]
}
]
}
}
]
}
into
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[-105.00341892242432, 39.75383843460583],
[-105.0008225440979, 39.751891803969535]
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[-105.0008225440979, 39.751891803969535],
[-104.99820470809937, 39.74979664004068]
]
}
}
]
}
and then apply style per layer
layer.on({
click: function(e) {
toggleLayerVisibility(e.target);
}
});
where
var selectedLayerId = null;
function toggleLayerVisibility(layer) {
if (selectedLayerId) {
geojson.resetStyle(layer);
selectedLayerId = null;
} else {
//hide a layer
layer.setStyle({
opacity: 0,
fillOpacity: 0.0
});
selectedLayerId = layer._leaflet_id;
}
}
Here is an example
var data = {
type: "FeatureCollection",
features: [
{
type: "Feature",
geometry: {
type: "GeometryCollection",
geometries: [
{
type: "LineString",
coordinates: [
[-105.00341892242432, 39.75383843460583],
[-105.0008225440979, 39.751891803969535]
]
},
{
type: "LineString",
coordinates: [
[-105.0008225440979, 39.751891803969535],
[-104.99820470809937, 39.74979664004068]
]
},
{
type: "LineString",
coordinates: [
[-104.99820470809937, 39.74979664004068],
[-104.98689651489258, 39.741052354709055]
]
}
]
}
},
{
type: "Feature",
geometry: {
type: "MultiPolygon",
coordinates: [
[
[
[-105.00432014465332, 39.74732195489861],
[-105.00715255737305, 39.7462000683517],
[-105.00921249389647, 39.74468219277038],
[-105.01067161560059, 39.74362625960105],
[-105.01195907592773, 39.74290029616054],
[-105.00989913940431, 39.74078835902781],
[-105.00758171081543, 39.74059036160317],
[-105.00346183776855, 39.74059036160317],
[-105.00097274780272, 39.74059036160317],
[-105.00062942504881, 39.74072235994946],
[-105.00020027160645, 39.74191033368865],
[-105.00071525573731, 39.74276830198601],
[-105.00097274780272, 39.74369225589818],
[-105.00097274780272, 39.74461619742136],
[-105.00123023986816, 39.74534214278395],
[-105.00183105468751, 39.74613407445653],
[-105.00432014465332, 39.74732195489861]
],
[
[-105.00361204147337, 39.74354376414072],
[-105.00301122665405, 39.74278480127163],
[-105.00221729278564, 39.74316428375108],
[-105.00283956527711, 39.74390674342741],
[-105.00361204147337, 39.74354376414072]
]
],
[
[
[-105.00942707061768, 39.73989736613708],
[-105.00942707061768, 39.73910536278566],
[-105.00685214996338, 39.73923736397631],
[-105.00384807586671, 39.73910536278566],
[-105.00174522399902, 39.73903936209552],
[-105.00041484832764, 39.73910536278566],
[-105.00041484832764, 39.73979836621592],
[-105.00535011291504, 39.73986436617916],
[-105.00942707061768, 39.73989736613708]
]
]
]
}
}
]
};
var map = L.map("map").setView([39.74739, -105], 14);
L.tileLayer(
"https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw",
{
maxZoom: 18,
attribution:
'Map data © OpenStreetMap contributors, ' +
'CC-BY-SA, ' +
'Imagery © Mapbox',
id: "mapbox.light"
}
).addTo(map);
var data_flatten = turf.flatten(data);
var geojson = L.geoJSON(data_flatten, {
style: {
fillColor: '#1c9099',
weight: 8
},
onEachFeature: onEachFeature
}).addTo(map);
var selectedLayerId = null;
function toggleLayerVisibility(layer) {
if (selectedLayerId) {
geojson.resetStyle(layer);
selectedLayerId = null;
} else {
//hide a layer
layer.setStyle({
opacity: 0,
fillOpacity: 0.0
});
selectedLayerId = layer._leaflet_id;
}
}
function onEachFeature(feature, layer) {
layer.on({
click: function(e) {
toggleLayerVisibility(e.target);
}
});
}
#map {
width: 600px;
height: 400px;
}
<link
rel="stylesheet"
href="https://unpkg.com/leaflet#1.5.1/dist/leaflet.css"
integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
crossorigin=""
/>
<script
src="https://unpkg.com/leaflet#1.5.1/dist/leaflet.js"
integrity="sha512-GffPMF3RvMeYyc1LWMHtK8EbPv0iNZ8/oTtHPx9/cc2ILxQ+u905qIwdpULaqDkyBKgOaB57QTMg7ztg8Jm2Og=="
crossorigin=""
></script>
<script
type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/Turf.js/5.1.5/turf.js"
></script>
<div id="map"></div>
map.on('load', function () {
map.addSource("markers", {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [4.869929, 50.465887]
},
"properties": {
"marker-symbol": "pin-2"
"id or class?": ".."
}
}]
}
});
What you wrote on your code is good, you can add any properties in the properties part.
Please refer to my example : http://codepen.io/poupi/pen/qZeXdK