Efficiently Drawing a Large Number of Identifiable Polygons in Mapbox GL - mapbox

"I am using Mapbox GL to draw a large number of equal-sized polygons on the map. I am currently using the addLayer method to achieve this, but when the number of polygons becomes large, the performance of the map becomes very slow and it becomes difficult to use. Is there any other way to draw a large number of polygons that is more efficient and does not compromise the performance of the map? It is also important that each individual polygon is identifiable so that I can interact with it."
I manage to draw 50*50 rectangles, however, the page became really slow. This is my code implementation:
useEffect(() => {
const map = new mapboxgl.Map({
container: mapContainerRef.current,
style: "mapbox://styles/mapbox/light-v11",
center: origin,
zoom: 22, // starting zoom
});
map.on("load", () => {
// Add a data source containing GeoJSON data.
var iter;
var destination = [-86.872238, 40.46873];
for (var i = 0; i < 50; i++) {
for (var j = 0; j < 50; j++) {
var pixelOrigin = merc.px(origin, 22);
const pixelDest1 = [pixelOrigin[0], pixelOrigin[1] - 38];
const pixelDest2 = [pixelOrigin[0] + 38, pixelOrigin[1] - 38];
const pixelDest3 = [pixelOrigin[0] + 38, pixelOrigin[1]];
const cordDest1 = merc.ll(pixelDest1, 22);
const cordDest2 = merc.ll(pixelDest2, 22);
const cordDest3 = merc.ll(pixelDest3, 22);
if (j == 0) {
iter = cordDest3;
}
map.addSource("x: " + i + "y: " + j, {
type: "geojson",
data: {
type: "Feature",
geometry: {
type: "Polygon",
// These coordinates outline Maine.
coordinates: [
[origin, cordDest1, cordDest2, cordDest3, origin],
],
},
},
});
origin = cordDest1;
map.addLayer({
id: "x: " + i + "y: " + j,
type: "line",
source: "x: " + i + "y: " + j,
layout: {},
paint: {
"line-color": "#808080",
"line-width": 1,
},
});
}
origin = iter;
}
// Add a black outline around the polygon.
});
// Clean up on unmount
return () => map.remove();
}, []);

You should use only one source with all features and one layer to render them.
You can make the individual features identifiable via their properties (you can use Map#queryRenderedFeatures to access the property).
const features = [];
for (var i = 0; i < 50; i++) {
for (var j = 0; j < 50; j++) {
...
const feature = {
"type": "Feature",
"properties": {
id: "x:" + i + "y:" + j,
},
geometry: {
type: "Polygon",
coordinates: [[origin, cordDest1, cordDest2, cordDest3, origin]],
}
};
features.push(feature);
}
}
map.addSource("polygons", {
"type": "FeatureCollection",
"features": features,
});

Related

How to have Leaflet.draw display Imperial units while drawing?

This doesn't work for much of anything really that I can tell.
I want to remove the meters while drawing without having to do it in a hackish way. I am thinking I can just grab it and do some stuff outside of the normal functionality but would rather it just work.
While I'm drawing I want the tooltip to show imperial units.
Leaflet Version is currently 1.7.1. - I should upgrade.
Using CDN link for leaflet.draw currently.
'''
function mapDraw() {
var drawLayer = new L.featureGroup();
map.addLayer( drawLayer );
// Add the edit toolbar
let drawControl = new L.Control.Draw({
draw: {
circle: {
metric: false,
feet: true
},
polyline: {
metric: false,
feet: 'feet'
},
polygon: {
metric: false,
feet: 'feet'
},
rectangle: {
metric: false,
feet: true
},
},
edit: {
featureGroup: drawLayer
}
});
map.on(L.Draw.Event.CREATED, function (e) {
var type = e.layerType;
layer = e.layer;
if ( type === 'polygon' || type === 'rectangle' ) {
var area = L.GeometryUtil.geodesicArea( layer.getLatLngs()[0] );
var readableArea = L.GeometryUtil.readableArea( area );
layer.bindTooltip( readableArea );
} else if ( type === 'circle' ) {
var radius = layer.getRadius();
var area = Math.PI * radius ** 2;
readableArea = L.GeometryUtil.readableArea( area );
layer.bindTooltip( readableArea );
} else if ( type === 'polyline' ) {
var latlng = layer.getLatLngs();
var distance = 0;
for ( var i = 0; i < latlng.length - 1; i++ ) {
distance += latlng[i].distanceTo( latlng[i+1] );
}
distanceInFeet = distance * 100 / 2.54 / 12;
distanceInYards = distanceInFeet / 3;
distanceInMiles = distanceInYards / 1760;
layer.bindTooltip( distanceInFeet.toFixed(2) + ' ft' );
}
drawLayer.addLayer( layer );
});
map.addControl(drawControl);
}

Leaflet map. How to apply layer control to custom markers

I have a leaflet map with custom markers.
Recently I added Layer Control so that I can make use of a legend to filter the markers.
I've managed to add the legend however it appears to recreate all the markers on top of my custom ones and filters those instead.
I know I'm doing something wrong in the code but can't figure out where.
How can I filter my custom markers using the legend/layer control ?
Here is my jsfiddle
var markersA = [];
var markersB = [];
var markersC = [];
//Loop through the initial array and add to two different arrays based on the specified variable
for (var i = 0; i < data.length; i++) {
switch (data[i].Category) {
case 1:
markersA.push(L.marker([data[i].lat, data[i].lng]));
break;
case 2:
markersB.push(L.marker([data[i].lat, data[i].lng]));
break;
case 3:
markersC.push(L.marker([data[i].lat, data[i].lng]));
break;
default:
break;
}
}
//add the groups of markers to layerGroups
var groupA = L.layerGroup(markersA);
var groupB = L.layerGroup(markersB);
var groupC = L.layerGroup(markersC);
const myAPIKey = "***";
var tileLayer = {
'Filter drop off Points:': L.tileLayer('http://{s}.tile.cloudmade.com/{key}/22677/256/{z}/{x}/{y}.png')
};
const layer = 'https://api.maptiler.com/maps/uk-openzoomstack-night/{z}/{x}/{y}.png?key=tbRG4BpdjpPfpJ5rjwZY'
const layer2 = 'https://api.maptiler.com/maps/voyager/{z}/{x}/{y}.png?key=tbRG4BpdjpPfpJ5rjwZY'
var osm = L.tileLayer(layer)
const map = L.map('map', {
center: data[data.length - 12],
zoomControl: true,
zoom: 17,
layers: [osm, groupA, groupB, groupC]
});
// 'translate(0,' + (-outerRadius + 7) + ')')
var overlayMaps = {
"<img src= 'https://api.geoapify.com/v1/icon/?type=awesome&color=green&icon=tick&scaleFactor=2&apiKey=6dc7fb95a3b246cfa0f3bcef5ce9ed9a' height=24>Delivered": groupA,
"<img src= 'https://api.geoapify.com/v1/icon/?type=awesome&color=gold&icon=tick&scaleFactor=2&apiKey=6dc7fb95a3b246cfa0f3bcef5ce9ed9a' height=24>Left": groupB,
"<img src= 'https://api.geoapify.com/v1/icon/?type=awesome&color=red&icon=tick&scaleFactor=2&apiKey=6dc7fb95a3b246cfa0f3bcef5ce9ed9a' height=24>Returned": groupC
};
L.control.layers(tileLayer, overlayMaps, {
collapsed: false,
position: 'topright'
}).addTo(map);
var mygeojson = {
type: "FeatureCollection",
features: [],
};
var waypoints = []
var all_points = []
for (i = 0; i < data.length; i++) {
var lng = data[i].lng;
var lat = data[i].lat;
all_points.push([lng, lat]);
if (data[i].Category != 0) {
mygeojson.features.push({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [data[i].lng, data[i].lat]
},
"properties": {
"Icon": data[i].Icon,
"Status": data[i].Status,
"Customer": data[i].Customer,
"Supplier": data[i].Supplier,
"Item Size": data[i]["Item Size"],
"Address": data[i].Address,
"Customer Order": data[i]["Customer Order"],
"Notes": data[i].Notes,
"Instruction": data[i].Instruction
}
});
waypoints.push({
id: data[i].id,
lat: data[i].lat,
lng: data[i].lng
});
}
}
// console.log(mygeojson);
//console.log(all_points);
var polyline = L.polyline(lngLatArrayToLatLng(all_points)).addTo(map);
function lngLatArrayToLatLng(lngLatArray) {
return lngLatArray.map(lngLatToLatLng);
}
function lngLatToLatLng(lngLat) {
return [lngLat[1], lngLat[0]];
}
function IconStyle(feature, latlng) {
switch (feature.properties["Status"]) {
case "Delivered":
var deliveredIcon = new L.icon({
iconUrl: `https://api.geoapify.com/v1/icon/?type=awesome&color=green&icon=tick&scaleFactor=2&apiKey=${myAPIKey}`,
iconSize: [20, 32], // size of the icon
iconAnchor: [0, 22], // point of the icon which will correspond to marker's location
popupAnchor: [-3, -26] // point from which the popup should open relative to the iconAnchor
});
return L.marker(latlng, {
icon: deliveredIcon
});
case "Left":
var leftIcon = new L.icon({
iconUrl: `https://api.geoapify.com/v1/icon/?type=awesome&color=gold&icon=tick&scaleFactor=2&apiKey=${myAPIKey}`,
iconSize: [20, 32], // size of the icon
iconAnchor: [0, 22], // point of the icon which will correspond to marker's location
popupAnchor: [-3, -26] // point from which the popup should open relative to the iconAnchor
});
return L.marker(latlng, {
icon: leftIcon
});
case "Returned":
var returnedIcon = new L.icon({
iconUrl: `https://api.geoapify.com/v1/icon/?type=awesome&color=red&icon=tick&scaleFactor=2&apiKey=${myAPIKey}`,
iconSize: [20, 32], // size of the icon
iconAnchor: [0, 22], // point of the icon which will correspond to marker's location
popupAnchor: [-3, -26] // point from which the popup should open relative to the iconAnchor
});
return L.marker(latlng, {
icon: returnedIcon
});
}
}

How to draw a custom polygon over a Scatter Series google chart?

I have a Scatter Series with a set of points, like the one shown here. https://developers.google.com/chart/interactive/docs/gallery/scatterchart
The points are grouped and each group is shown in different color. I would like to draw a polygon around each group (convex hull). Looks like there is not a straightforward way to add polygons each with n boundary-points to the chart.
if you have an algorithm to find the boundary points,
you can use a ComboChart to draw both the scatter and line series...
use option seriesType to set the default type
use option series to customize the type for a particular series
in the following working snippet,
the algorithm used was pulled from --> Convex Hull | Set 1 (Jarvis’s Algorithm or Wrapping)
(converted from the Java version)
google.charts.load('current', {
packages: ['corechart']
}).then(function () {
var groupA = [
[0,3],[2,3],[1,1],[2,1],[3,0],[0,0],[3,3],[2,2]
];
var groupB = [
[11,11],[12,12],[12,10],[12,14],[13,13],[14,12],[15,12],[16,12]
];
var data = new google.visualization.DataTable();
data.addColumn('number', 'x');
data.addColumn('number', 'y');
data.addRows(groupA);
data.addRows(groupB);
addGroup('A', data, groupA)
addGroup('B', data, groupB)
var options = {
chartArea: {
bottom: 48,
height: '100%',
left: 36,
right: 24,
top: 36,
width: '100%'
},
height: '100%',
seriesType: 'line',
series: {
0: {
type: 'scatter'
}
},
width: '100%'
};
var chart = new google.visualization.ComboChart(document.getElementById('chart_div'));
drawChart();
window.addEventListener('resize', drawChart, false);
function drawChart() {
chart.draw(data, options);
}
function addGroup(group, dataTable, points) {
var polygon = convexHull(points);
var colIndex = dataTable.addColumn('number', group);
for (var i = 0; i < polygon.length; i++) {
var rowIndex = dataTable.addRow();
dataTable.setValue(rowIndex, 0, polygon[i][0]);
dataTable.setValue(rowIndex, colIndex, polygon[i][1]);
}
}
function orientation(p, q, r) {
var val = (q[1] - p[1]) * (r[0] - q[0]) -
(q[0] - p[0]) * (r[1] - q[1]);
if (val == 0) {
return 0; // collinear
} else if (val > 0) {
return 1; // clock wise
} else {
return 2; // counterclock wise
}
}
function convexHull(points) {
// must be at least 3 rows
if (points.length < 3) {
return;
}
// init
var l = 0;
var p = l;
var q;
var hull = [];
// find leftmost point
for (var i = 1; i < points.length; i++) {
if (points[i][0] < points[l][0]) {
l = i;
}
}
// move counterclockwise until start is reached
do {
// add current point to result
hull.push(points[p]);
// check orientation (p, x, q) of each point
q = (p + 1) % points.length;
for (var i = 0; i < points.length; i++) {
if (orientation(points[p], points[i], points[q]) === 2) {
q = i;
}
}
// set p as q for next iteration
p = q;
} while (p !== l);
// add back first hull point to complete line
hull.push(hull[0]);
// set return value
return hull;
}
});
html, body, #chart_div {
height: 100%;
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>

Dygraphs - How do I restrict drawing to canvas

I have a graph where I am using underlays to draw vertical lines between the points. I have a line of code that restricts these vertical lines to NOT draw outside the active canvas. But when I use this underlayCallback, the 'points' are still drawn outside the canvas. If I remove my underlayCallback, the points are restricted to the canvas as one would expect. Here is what they look like and my code. (Sorry, the site is too secure to provide working sample.)
g[i] = new Dygraph(thisdiv, mylines, {
labels: graphlbls[i],
ylabel: graphunits[i].capitalizeFirstLetter(),
xlabel: '',
xLabelHeight:15,
yLabelWidth:15,
rightGap: 5,
labelsDivStyles: {
'text-align': 'right',
'background': 'none'
},
colors: ['#D48513','#1D6EB5'],
title: graphtitles[i],
titleHeight:23,
drawPoints: true,
showRoller: false,
drawXGrid: false,
drawYGrid: true,
strokeWidth: 0,
pointSize: 4,
highlightCircleSize: 6,
gridLineColor: "#ddd",
axisLabelFontSize: 12,
xAxisHeight: 20,
valueRange: [minval, maxval],
rangeSelectorHeight: 30,
showRangeSelector: true,
rangeSelectorPlotFillColor: '#ffffff',
rangeSelectorPlotStrokeColor: '#ffffff',
interactionModel: Dygraph.defaultInteractionModel,
axes: {
x: {
valueFormatter: function (ms) {
var d = new Date(ms);
var day = "0"+d.getDate();
var month = "0"+(d.getMonth()+1);
var year = d.getFullYear();
var hour = "0"+ d.getHours();
var min = "0"+d.getMinutes();
var p = "AM";
if (hour > 12) { p = "PM"; hour = hour - 12; }
if (df == 0) var dd = month.slice(-2)+"/"+day.slice(-2)+"/"+year;
if (df == 1) var dd = day.slice(-2)+"/"+month.slice(-2)+"/"+year;
if (tf == 0) var tt = hour.slice(-2)+":"+min.slice(-2)+" "+p+" ";
if (tf == 1) var tt = hour.slice(-2)+":"+min.slice(-2)+" ";
return dd + " - " + tt;
}
}
},
underlayCallback: function(ctx, area, g) {
//if (typeof(g[i]) == 'undefined') return; // won't be set on the initial draw.
var range = g.xAxisRange();
var rows = g.numRows();
// get max and min y
for (var i = 0; i < rows; i++) {
miny = 99999;
maxy = -99999;
xx = g.getValue(i,0);
if (xx < range[0] || xx > range[1]) continue; // constrain to graph canvas
for (var j=1; j<= range.length; j++) {
if (g.getValue(i,j) <= miny) miny = g.getValue(i,j);
if (g.getValue(i,j) >= maxy) maxy = g.getValue(i,j);
}
p1 = g.toDomCoords(xx, miny);
p2 = g.toDomCoords(xx, maxy);
ctx.strokeStyle = "rgba(192,192,224,1)";
ctx.lineWidth = 1.0;
ctx.beginPath();
ctx.moveTo(p1[0], p1[1]);
ctx.lineTo(p2[0], p2[1]);
ctx.closePath();
ctx.stroke();
ctx.restore();
}
}
});
You're calling ctx.restore() many times without corresponding calls to ctx.save(). This pops off dygraphs' own drawing context, including the clipping rectangle. Make one call to save at the top of your underlayCallback and one to restore at the end.
Stepping back a bit, what you're doing might be easier with a custom plotter, rather than an underlayCallback.

Integrating / adding Google Earth View to my map

I am creating an interactive map for a non profit association "Friends of Knox Mountain Park" but I am getting trouble with the Google Earth view.
I've been searching on the web for weeks and none of the solutions I found works for me. Can someone take a look of the code and let me know what I should do to include Google Earth View in the map? Thanks in advance.
The online project: http://www.virtualbc.ca/knoxmountain/
And this is the javascript file (mapa2.js) containing the google map's code:
google.load('earth', '1');
var map;
var googleEarth;
var gmarkers = [];
var iconShadow = new google.maps.MarkerImage('icons/shadow.png',
new google.maps.Size(46, 42),
new google.maps.Point(0,0),
new google.maps.Point(13, 42));
var sites = [
['Apex Trail - Shelter',49.91174271, -119.48507050, 4, '<img src="images/apex_point_high.jpg">','magenta','14'],
['Apex Trail',49.91286999, -119.48413424, 3, '<img src="images/apex_point_low.jpg">','lemon','1'],
['Gordon Trail',49.91971281, -119.47954356, 2, '<img src="images/apex_point_low.jpg">','lemon','1'],
['Paul Tomb Bay',49.92555541, -119.47710250, 1, '<img src="images/tomb_bay.jpg">','lemon','1']
];
var infowindow = null;
var overlay;
// Used to make Google Map quard coords to MapCruncher/BingMaps quard coords
function TileToQuadKey ( x, y, zoom)
{
var quad = "";
for (var i = zoom; i > 0; i--)
{
var mask = 1 << (i - 1);
var cell = 0;
if ((x & mask) != 0)
cell++;
if ((y & mask) != 0)
cell += 2;
quad += cell;
}
return quad;
}
function init() {
var centerMap = new google.maps.LatLng(49.909671, -119.482241);
var myOptions = {
zoom: 10,
center: centerMap,
mapTypeId: google.maps.MapTypeId.SATELLITE
}
map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
// Create the tile layers
// ASTER Tile Layer
myASTEROptions = {
getTileUrl : function (a,b) {
return "http://www.virtualbc.ca/knoxmountain/map/" + TileToQuadKey(a.x,a.y,b) + ".png";
},
isPng: true,
opacity: 1.0,
tileSize: new google.maps.Size(256,256),
name: "ASTER",
minZoom:13,
maxZoom:20
}
ASTERMapType = new google.maps.ImageMapType( myASTEROptions );
map.overlayMapTypes.insertAt(0, ASTERMapType);
// Aerial Tile Layer
myAerialOptions = {
getTileUrl : function (a,b) {
return "http://www.virtualbc.ca/knoxmountain/map/" + TileToQuadKey(a.x,a.y,b) + ".png";
},
isPng: true,
opacity: 1.0,
tileSize: new google.maps.Size(256,256),
name: "Aerial",
minZoom:15,
maxZoom:21
}
AerialMapType = new google.maps.ImageMapType( myAerialOptions );
map.overlayMapTypes.insertAt(1, AerialMapType);
var panorama = new google.maps.StreetViewPanorama(map.getDiv());
panorama.setVisible(false);
panorama.set('enableCloseButton', true);
map.setStreetView(panorama);
panorama.setPosition(centerMap);
setMarkers(map, sites);
setZoom(map, sites);
infowindow = new google.maps.InfoWindow({
content: "Loading..."
});
googleEarth = new GoogleEarth(map);
google.maps.event.addListenerOnce(map, 'tilesloaded', addOverlays);
}
/*
This functions sets the markers (array)
*/
function setMarkers(map, markers) {
for (var i = 0; i < markers.length; i++) {
var site = markers[i];
var siteLatLng = new google.maps.LatLng(site[1], site[2]);
var marker = new google.maps.Marker({
position: siteLatLng,
map: map,
title: site[0],
zIndex: site[3],
html: site[4],
// Markers drop on the map
animation: google.maps.Animation.DROP,
icon: 'http://www.virtualbc.ca/knoxmountain/icons/icon.png',
shadow: iconShadow
});
gmarkers.push(marker);
google.maps.event.addListener(marker, "click", function () {
infowindow.setContent(this.html);
infowindow.open(map, this);
});
}
}
/*
Set the zoom to fit comfortably all the markers in the map
*/
function setZoom(map, markers) {
var boundbox = new google.maps.LatLngBounds();
for ( var i = 0; i < markers.length; i++ )
{
boundbox.extend(new google.maps.LatLng(markers[i][1], markers[i][2]));
}
map.setCenter(boundbox.getCenter());
map.fitBounds(boundbox);
}
// This function picks up the click and opens the corresponding info window
function myclick(i) {
google.maps.event.trigger(gmarkers[i-1], "click");
}
google.maps.event.addDomListener(window, 'load', init);
The first issue I notice with your site is you are linking to http://www.virtualbc.ca/src/googleearth-compiled.js which does not exist.