Is there a way make a reusable shape object? - javascript

I'm using LeafletJS to make a custom lab layout map and I plan to put in some rectangle layers to show whether or not areas are in use. Currently I have a working test case using coordinates to define each shape, but is there a way I can create a standard sized shape object which can be called and fed a single coordinate to center itself on?
Here is the current code from my Angular controller if it helps.
function showMap() {
var map = L.map('mapid', {
crs: L.CRS.Simple,
maxZoom: 4,
attributionControl: false
}).setView([0, 0], 1),
southWest = map.unproject([0, 4096], map.getMaxZoom()),
northEast = map.unproject([4096, 0], map.getMaxZoom()),
bounds = L.latLngBounds(southWest, northEast);
L.tileLayer('images/4231/{z}/{x}/{y}.png', {
minZoom: 1,
maxZoom: 4,
center: [0, 0],
noWrap: true,
bounds: bounds
}).addTo(map);
var testBench = [{
number: "1A1",
coord1: "-48.6",
coord2: "6",
coord3: "-81.4",
coord4: "71",
inUse: true
}, {
number: "1A2",
coord1: "-48.5",
coord2: "71",
coord3: "-81",
coord4: "137",
inUse: false
}, {
number: "1A3",
coord1: "-48.5",
coord2: "137",
coord3: "-81",
coord4: "202",
inUse: true
}];
angular.forEach(testBench, function(item, index) {
var location = [
[item.coord1, item.coord2],
[item.coord3, item.coord4]
],
color;
switch (item.inUse) {
case true:
color = "red"
break;
case false:
color = "green"
break;
}
L.rectangle(location, {
color: color,
weight: 1
}).bindPopup("Bench Number is: " + item.number).addTo(map);
})
map.setMaxBounds(bounds);
}
Eventually the bench info will be pulled from a DB rather than a variable and there will be hundreds of benches, so I'm looking to streamline the positioning layout as much as possible.

is there a way I can create a standard sized shape object which can be called and fed a single coordinate to center itself on?
In Leaflet, no.
Leaflet vector features, or L.Paths (L.Polylines and L.Polygons) are defined by their coordinates, not by their centroid and a series of offsets to that centroid.
You might want to implement a simple Factory design pattern to create regularly-shaped features giving only a center point, though.

Related

Challenge for polygon display within leaflet

We have a specific design challenge for polygon display within leaflet (latest version).
We have polygons which are rendered with a solid border as well as a semi-transparent background.
We are looking for a way to draw a solid borderline as well as a wider "inline" border and no background.
Note: the question is for polygons not rectangular. The below image
and code is just for example.
Is there any way to achieve this?
var polygon = L.polygon([
[ 51.72872938200587, -2.415618896484375 ],
[ 51.72872938200587, -2.080535888671875 ],
[ 51.901918172561714, -2.080535888671875 ],
[ 51.901918172561714, -2.415618896484375 ],
[ 51.72872938200587, -2.415618896484375 ]
],{
color:'#2F538F',
fillOpacity: 0.9,
fillColor: '#BFBFBF',
}).addTo(map);
This is achievable by utilizing leaftlet's class extension system.
To start with, leaflet's class diagram could be consulted to determine where the extension is needed. As a general rule, first try to extend classes towards the root, and prefer L.Class.extend over L.Class.include.
Working Solution:
Codesandbox
One approach is hooking into the rendering process. In the following example, L.Canvas is extended to a custom L.Canvas.WithExtraStyles class (leaflet's plugin building guidelines). The custom Renderer is then provided to map.
In this approach, note that multiple borders and fills (both inset and outset) could be provided using the extraStyles config.
extraStyle custom property accepts Array of PathOptions. With an additional inset, whose value could be positive or a negative number of pixels representing the offset form the border of the main geometry. A negative value of inset will put the border outside of the original polygon.
While implementing such customizations, special care must be taken to make sure leaflet is not considering the added customizations as separate geometric shapes. Otherwise interactive functionalities e.g. Polygon Edit or Leaflet Draw will have unexpected behaviour.
// CanvasWithExtraStyles.js
// First step is to provide a special renderer which accept configuration for extra borders.
// Here L.Canvas is extended using Leaflet's class system
const styleProperties = ['stroke', 'color', 'weight', 'opacity', 'fill', 'fillColor', 'fillOpacity'];
/*
* #class Polygon.MultiStyle
* #aka L.Polygon.MultiStyle
* #inherits L.Polygon
*/
L.Canvas.WithExtraStyles = L.Canvas.extend({
_updatePoly: function(layer, closed) {
const centerCoord = layer.getCenter();
const center = this._map.latLngToLayerPoint(centerCoord);
const originalParts = layer._parts.slice();
// Draw extra styles
if (Array.isArray(layer.options.extraStyles)) {
const originalStyleProperties = styleProperties.reduce(
(acc, cur) => ({ ...acc, [cur]: layer.options[cur] }),
{}
);
const cx = center.x;
const cy = center.y;
for (let eS of layer.options.extraStyles) {
const i = eS.inset || 0;
// For now, the algo doesn't support MultiPolygon
// To have it support MultiPolygon, find centroid
// of each MultiPolygon and perform the following
layer._parts[0] = layer._parts[0].map(p => {
return {
x: p.x < cx ? p.x + i : p.x - i,
y: p.y < cy ? p.y + i : p.y - i
};
});
//Object.keys(eS).map(k => layer.options[k] = eS[k]);
Object.keys(eS).map(k => (layer.options[k] = eS[k]));
L.Canvas.prototype._updatePoly.call(this, layer, closed);
}
// Resetting original conf
layer._parts = originalParts;
Object.assign(layer.options, originalStyleProperties);
}
L.Canvas.prototype._updatePoly.call(this, layer, closed);
}
});
// Leaflet's conventions to also provide factory methods for classes
L.Canvas.withExtraStyles = function(options) {
return new L.Canvas.WithExtraStyles(options);
};
// --------------------------------------------------------------
// map.js
const map = L.map("map", {
center: [52.5145206, 13.3499977],
zoom: 18,
renderer: new L.Canvas.WithExtraStyles()
});
new L.tileLayer(
"https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_nolabels/{z}/{x}/{y}.png",
{
attribution: `attribution: '© OpenStreetMap, © CARTO`,
detectRetina: true
}
).addTo(map);
// Map center
const { x, y } = map.getSize();
// Left Polygon
const polyStyle1 = {
color: '#2f528f',
extraStyles: [
{
color: 'transparent',
weight: 10,
fillColor: '#d9d9d9'
}
]
};
// Sudo coordinates are generated form map container pixels
const polygonCoords1 = [
[0, 10],
[300, 10],
[300, 310],
[0, 310]
].map(point => map.containerPointToLatLng(point));
const polygon1 = new L.Polygon(polygonCoords1, polyStyle1);
polygon1.addTo(map);
// Right Polygon
const polyStyle2 = {
fillColor: "transparent",
color: '#2f528f',
extraStyles: [
{
inset: 6,
color: '#d9d9d9',
weight: 10
}
]
};
const polygonCoords2 = [
[340, 10],
[640, 10],
[640, 310],
[340, 310]
].map(point => map.containerPointToLatLng(point));
const polygon2 = new L.Polygon(polygonCoords2, polyStyle2);
polygon2.addTo(map);
<script src="https://unpkg.com/leaflet#1.6.0/dist/leaflet.js"></script>
<link href="https://unpkg.com/leaflet#1.6.0/dist/leaflet.css" rel="stylesheet"/>
<div id="map" style="width: 100vw; height: 100vw">0012</div>
Ideal Solution:
Implement a plugin as a separate npm module.
Try to extend or hook into Renderer itself instead of separately extending L.Canvas and L.SVG.
Hook the cusomization into base class Path instead of individual shapes: Polygon, Polyline or Circle.
Use the Recatngle/Polygon method.
// define rectangle geographical bounds
var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
// create an orange rectangle
L.rectangle(bounds, {}).addTo(map);
The Use options to get the desired effect on the lines. Options are inherited from polyline options
There you can tweak color, opacity, fill, fillColor, fillOpacity and fillRule to get the desired effect on the lines

Leaflet: Getting Latitude/Longitude in createTile using CRS.Simple

I have a simple fictional map that I want to control using Leaflet. It is a flat 2D plane and its "latitude/longitude"/coordinate system spans from [0,0] to [999,999].
I have customized the map as follows:
window.map = L.map('leaflet-map', {
crs: L.CRS.Simple,
center: [500, 500],
zoom: 13,
maxBounds: [
[0, 0],
[999, 999],
],
layers: [new MyLayer()],
});
To draw this map, I've created a new layer, MyLayer, which extends gridLayer:
export var MyLayer = GridLayer.extend({
createTile: function(coords, done) {
var error;
var xmlhttprequest = new XMLHttpRequest();
xmlhttprequest.addEventListener('readystatechange', function() {
done(error, dothething());
});
xmlhttprequest.open('GET', /* url */);
xmlhttprequest.send();
},
});
The problem I have is the URL accepts the [0,0] to [999,999] coordinate system as parameters but I can't find how to actually get those. I understand there may be some decimal element but I can floor that as appropriate.
When centered on [500, 500, 13] the coords object contains { x: 15516, y: -21558, z: 13 }. When passed to L.CRS.Simple.pointToLatLng(coords, coords.z) I get { lat: 2.631591796875, lng: 1.89404296875 }.
I've downloaded the source code in an attempt to understand how this transformation happens from Map._move(center, zoom, data) but all that appears to do is call this.options.crs.latLngToPoint(), which is exactly what I reverse in L.CRS.Simple.pointToLatLng. I'm frankly at a loss.
First of all, I encourage you to read the Leaflet tutorial on L.CRS.Simple once again. Let me quote a relevant bit from there :
In a CRS.Simple, one horizontal map unit is mapped to one horizontal pixel, and idem with vertical. [...] we can set minZoom to values lower than zero:
So you have no reason to go down to zoom level 13 on your L.CRS.Simple map by default, really. For a [0,0]-[999,999] map, use zoom level zero for an overview, or use map.fitBounds([[0,0],[999,999]]).
The values that the createTile() method receives are tile coordinates, not CRS coordinates. A level-0 tile is split into four level-1 tiles, sixteen level-2 tiles, 64 level-3 tiles, and so on, up to 2^13 tiles at level 13. This is easier to visualize by playing with a L.GridLayer that displays the tile coordinates, like:
var grid = L.gridLayer({
attribution: 'Grid Layer',
// tileSize: L.point(100, 100),
});
grid.createTile = function (coords) {
var tile = L.DomUtil.create('div', 'tile-coords');
tile.innerHTML = [coords.x, coords.y, coords.z].join(', ');
return tile;
};
map.addLayer(grid);
Second: you want to use the internal _tileCoordsToBounds method, defined at L.GridLayer. Give it a set of tile coordinates, and you'll get back a L.LatLngBounds with the area covered by such a tile.
The following example (try it live here) should put you on track. Remember to read the documentation for L.LatLngBounds as well.
var grid = L.gridLayer({
attribution: 'Grid Layer',
// tileSize: L.point(100, 100),
});
grid.createTile = function (coords) {
var tile = L.DomUtil.create('div', 'tile-coords');
var tileBounds = this._tileCoordsToBounds(coords);
tile.innerHTML = [coords.x, coords.y, coords.z].join(', ') +
"<br>" + tileBounds.toBBoxString();
return tile;
};
map.addLayer(grid);

How do switch Leaflet coordinates to where 0,0 is southwest?

I am working on a leaflet project where I need the coordinates 0 to 4096 and which the leaflet coordinates that I need are the following:
0,0 bottom left (southWest)
0,4096' top left (northWest)
4096',4096' top right (northEast)
4096',0 bottom right (southEast)
I have tried many things to get the coordinates to map as needed but no luck. Here is my jsfiddle with my example, which has a console.log when you click on the map which shows the coordinates. If you click in the top left you will see its 0,0 and if you click in the bottom right you will see 4096,4096 which is backwards from whats needed. Here is my code that I have
url = 'http://{s}.tile.osm.org/{z}/{x}/{y}.png';
weight = 4096;
height = 4096;
mapMinZoom = 1;
mapMaxZoom = 7;
map = L.map('map', {
maxZoom: mapMaxZoom,
minZoom: mapMinZoom,
noWrap: true,
detectRetina: true
});
unproject = function(coord) {
return map.unproject(coord, 4);
};
southWest = unproject([0, 0]);
northEast = unproject([height, weight]);
map.setMaxBounds(new L.LatLngBounds(southWest, northEast));
tileLayer = L.tileLayer(url, {
minZoom: mapMinZoom,
maxZoom: mapMaxZoom,
noWrap: true,
tileSize: 256,
detectRetina: false
}).addTo(map);
map.setView(unproject([0, 0]), 2);
map.on('click', function(e) {
var coords, latLong;
latLong = new L.LatLng(e.latlng.lat, e.latlng.lng);
coords = map.project(latLong, 4);
console.log("x="+coords.x+", y="+coords.y);
});
If I understand correctly, you would like a map coordinates like this:
[0, 4096] .. [4096, 4096]
.........................
.........................
[0, 0] ........ [4096, 0]
It is good as vertical coordinate increases while going up (like latitude on Leaflet) and horizontal coordinate increases while going right (like longitude on Leaflet). So you do not seem to need to invert the vertical axis.
As for the order of the coordinates, unfortunately Leaflet uses latitude first, longitude second, and it is almost impossible to switch them natively. However, you can easily build a wrapper method that just swaps the coordinates. (similar to Leaflet LatLngBounds with simpler CRS & projection, but no need to use negative vertical coordinate -y since in your case you want it in the same direction as latitude)
Then another question would be how you want the coordinates in-between those 4 corners. The default CRS (Web Mercator) is not linear along the vertical axis. If you need your coordinates to be linear, you should use L.CRS.Simple. See Leaflet tutorial for non-Earth-based maps.

Leaflet JS polygon on a same map

I use Leaflet JS, and I need same polygon on a one map
Now:
I need:
Thank's for your help!
If I set the noWrap:true on the tileLayer or the worldCopyJump:true I get:
Fiddle – jsfiddle.net/paRxe/5
var map = L.map('mapId',{
center: [35.67989, 139.76463],
zoom: 2,
// worldCopyJump: true,
maxZoom: 18,
minZoom: 1,
// reuseTiles: true,
// continuousWorld: trie
// reuseTiles: true,
// continuousWorld: true
worldCopyJump: true
}
);
Your 2 polygons are actually part of a multipolygon, forming a single feature.
You could use Turf.js for example, in order to 1) translate one of the part by 360 degrees, and 2) merge those 2 parts. Then record the new feature geometry to replace your current GeoJSON data.
For step 1), you should also be able to use directly Leaflet with latLng.wrap() method.

Highlight polygon and tint rest of map using Google Maps [duplicate]

This question already has an answer here:
Google Maps API v3 Highlight Country Border without using Polygons
(1 answer)
Closed 8 months ago.
I'd like to display a highlighted polygon using Google Maps. The idea is that the polygon in question would be displayed normally and the rest of the map should be darkened a little bit.
So, is this even possible do this with Google Map API's? If yes, with what version (v2, v3)? Would it be easier to do it with other map toolkits, like openlayers?
PS: One idea I had, was to build an inverse polygon (in this example, the whole world minus the shape of austria) and then display a black colored overlay with transparency using this inverted polygon. But that seems to be quite complicated to me.
Google Maps API v3 lets you draw polygons with holes. Here's Google's Pentagon example. It is much easier than trying to invert a polygon. Basically, create coordinates for a giant polygon that is bigger than you would ever need. That will always be the first polygon in your polygon array. The area you are highlighting will always be the second polygon.
Here's some code to change Google's Bermuda Triangle demo to use a polygon with a hole:
var everythingElse = [
new google.maps.LatLng(0, -90),
new google.maps.LatLng(0, 90),
new google.maps.LatLng(90, -90),
new google.maps.LatLng(90, 90),
];
var triangleCoords = [
new google.maps.LatLng(25.774252, -80.190262),
new google.maps.LatLng(18.466465, -66.118292),
new google.maps.LatLng(32.321384, -64.75737),
new google.maps.LatLng(25.774252, -80.190262)
];
bermudaTriangle = new google.maps.Polygon({
paths: [everythingElse, triangleCoords],
strokeColor: "#000000",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#000000",
fillOpacity: 0.5
});
bermudaTriangle.setMap(map);
USING GEOJSON
<div id="googleMap" style="width:500px;height:380px;"></div>
// define map properties
var mapProp = {
center: new google.maps.LatLng(23.075984, 78.877656),
zoom: 5,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
//create google map
var map = new google.maps.Map(document.getElementById("googleMap"), mapProp);
// define geojson
var geojson = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[0, 90],
[180, 90],
[180, -90],
[0, -90],
[-180, -90],
[-180, 0],
[-180, 90],
[0, 90]
],
[
[79.56298828125, 25.18505888358067],
[76.53076171875, 21.37124437061832],
[83.38623046875, 21.24842223562701],
[79.56298828125, 25.18505888358067]
]
]
},
"properties": {}
}]
};
//add geojson to map
map.data.addGeoJson(geojson);
incase of external geojson file use
map.data.loadGeoJson('url-to-geojson-file');
note: google used .json as extension for geojson file and not .geojson
https://developers.google.com/maps/documentation/javascript/datalayer
create your geojson here
https://google-developers.appspot.com/maps/documentation/utils/geojson/
working example
https://jsfiddle.net/841emtey/5/
With regard to:
the rest of the map should be darkened a little bit.
This can be done with with Maps API v3 using Styled Maps.
There's even a Styled Maps Wizard where you can play with the settings, and then press Show JSON to get the array to pass as the first argument to new google.maps.StyledMapType.
To get the effect you want just reduce the Lightness for everything, in the wizard you'll want to see this in the Map Style box on the right:
Feature type: all
Element type: all
Lightness: -70
Which will export to:
[
{
"stylers": [
{ "lightness": -70 }
]
}
]
And look like this.

Categories

Resources