Leaflet: Edit button not enabled when drawing over an ImageLayer - javascript

All:
Perhaps I am not doing this the correct way. The way Leaflet draw works is that as soon as you draw something on a map using the draw control (circle, square, polygon, whatever) the delete/edit button becomes enabled. In my map, with clicks of links on my web page that contains the Leaflet map, a user can add many imageOverlays, but lets just say for this scenario the user clicked and added one imageOverlay. The imageOverlay appears fine on the map. Now the user wants to draw a shape - say a circle - on the map. When the user clicks on the circle icon in the draw control and paints it on the map the delete/edit button does NOT become enabled. I am stumped. I suspect that this may have something to do with layers /layering. I need to have that delete/edit button behave as it would if there were no imageLayers on the map. Can somebody please enlighten me? Thank you.
My React.js code adds the imageLayers to array and the array of images is then added to a L.featureGroup.
When drawing a shape, the layertype is added to the same L.featureGroup.
My code:
In React.js:
let myFeatureGroup = new L.FeatureGroup();
let imageLayersArray = [];
// My function to load imageLayers:
const applyLayer = (mapSubType) => {
const mapSubTypeCaps = mapSubType.toUpperCase();
let dlaBounds = null;
const someOptions = {
opacity: 0.3,
interactive: true,
bubblingMouseEvents: true,
className: 'imageborder',
zIndex: 5,
mapsubtypelayer: mapSubType, // required in order to identify a layer upon removal
};
// The 8 PNG image coordinates:
const ImageCoords = [
[[-90, 0], [0, 90]],
[[0, 0], [90, 90]],
[[-90, -90], [0, 0]],
[[0, -90], [90, 0]],
[[-180, 0], [-90, 90]],
[[90, 0], [180, 90]],
[[-180, -90], [-90, 0]],
[[90, -90], [180, 0]],
];
let counter = 0;
ImageCoords.map((coordEntry) => {
counter++;
const useThisURL = `https://dla-maps-storage.s3.amazonaws.com/DLAMAP${mapSubTypeCaps}${counter}.png`;
// Need to flip the coordinates from lng/lat to lat/lng for leaflet:
let arrayofcoordinates = [];
const flippedarrayofcoordinates = [];
arrayofcoordinates = coordEntry;
arrayofcoordinates.map((pairofcoordinates) => {
const workAry = [];
// flip the coordinates from lng/lat config to lat/lng:
workAry[0] = pairofcoordinates[1];
workAry[1] = pairofcoordinates[0];
flippedarrayofcoordinates.push(workAry);
});
dlaBounds = L.latLngBounds(flippedarrayofcoordinates);
const anImage = L.imageOverlay(
useThisURL,
dlaBounds,
someOptions,
)
// add the imagelayer to the imageLayersArray:
// Somebody along the way recommended to do this
imageLayersArray.push(anImage);
anImage.on('click', (leafletEvent) => {
handleImageClick(leafletEvent);
});
});
myFeatureGroup = L.featureGroup(imageLayersArray).addTo(map);
};
// Draw Event Created:
map.on(L.Draw.Event.CREATED, (e) => {
console.log('L.Draw.Event.CREATED start:: ');
const { layer } = e;
switch (e.layerType) { // types: polyline, polygon, rectangle
case 'circle':
// Call rest endpoint with lat/lng coordinates
break;
case 'marker':
// Call rest endpoint with lat/lng coordinates
break;
case 'polyline':
// Call rest endpoint with lat/lng coordinates
break;
default:
// POLYGON OR RECTANGLE DRAWN:
// Call rest endpoint with lat/lng coordinates
break;
}
myFeatureGroup.addLayer(layer);
console.log('L.Draw.Event.CREATED end:: ');
});

Related

HereMaps javascript API displaying minor_roads at zoom level 12

I would like to show minor_roads(residential) at zoom level 12.
However, it appears that by default (vector tile) data source doesn't include minor roads at 12.
I tried to set road width for zoom level 12 but it doesn't appear to work
minor_road_width: [[6, 1px], [7, 1px], [8, 1px], [9, 1px], [11, 1px], [12, 1px], [13, 1px],[14, 1px], [15, 2px]]
You can set this properties but they will not be applicable for minor roads because they are not retrieved by Vector Tile API https://developer.here.com/documentation/vector-tiles-api/dev_guide/index.html because minor roads are returned only for zoom level >= 13
You need to draw such roads by your self to utilize Fleet Telematics API (FTA) using ROAD_GEOM layer. See please this example on https://jsfiddle.net/L5vsjtwd/
/**
* Shows the postcode layer provided by Platform Data Extension REST API
* https://developer.here.com/platform-extensions/documentation/platform-data/topics/introduction.html
*
* #param {H.Map} map A HERE Map instance within the application
*/
function showPostcodes(map, bubble){
var service = platform.getPlatformDataService();
service.searchByBoundingBox(
["CARTO_LINE_DO3", "CARTO_LINE_DO2"],
["CARTO_ID","CARTO_ID"],
map.getViewModel().getLookAtData().bounds.getBoundingBox(),
function(arrD){
for(var i=0; i<arrD.length; i++){
//console.log(arrD[i].getCell("FEATURE_TYPE"), arrD[i].getCell("WKT").toString());
map.addObject(new H.map.Polyline(
arrD[i].getCell("WKT"), { style: { lineWidth: 4 }}
));
}
},
console.error
);
}
/**
* Boilerplate map initialization code starts below:
*/
//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 - not specificing a location will give a whole world view.
var map = new H.Map(document.getElementById('map'),
defaultLayers.vector.normal.map, {
pixelRatio: window.devicePixelRatio || 1
});
// add a resize listener to make sure that the map occupies the whole container
window.addEventListener('resize', () => map.getViewPort().resize());
map.setCenter({lat:52.5159, lng:13.3777});
map.setZoom(9);
//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));
// Create the default UI components
var ui = H.ui.UI.createDefault(map, defaultLayers);
// create info bubble that is used to display the postcode data
bubble = new H.ui.InfoBubble(map.getCenter(), {
content: ''
});
bubble.close();
ui.addBubble(bubble);
map.addEventListener("mapviewchangeend", function(){
showPostcodes(map, bubble);
});
// Now use the map as required...
//setTimeout(function(){showPostcodes(map, bubble);}, 2000);
In above example is similar issue (no showing rails for some zoom level) but using FTA (layers "CARTO_LINE_DO3", "CARTO_LINE_DO2") with JS API is possible.
Documentation:
https://demo.support.here.com/pde/layers?region=WEU&release=latest&url_root=pde.api.here.com
https://demo.support.here.com/pde/layer?region=WEU&release=latest&url_root=pde.api.here.com&layer=ROAD_GEOM_FC5
https://demo.support.here.com/pde/maps?url_root=pde.api.here.com
https://developer.here.com/documentation/fleet-telematics/dev_guide/index.html
https://developer.here.com/documentation/content-map-attributes/dev_guide/index.html

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);

Drawn polygon does not show in openlayers

I have to draw a polygon on openlayers Map. This is my code:
draw = new Draw({
source: this.vectorSource,
type: 'Polygon'
})
draw.on('drawend', e => {
// sol 1, result is not as required
let coords = e.feature.getGeometry().getCoordinates()
//sol 2, give correct results, but drawn polygon gone
let coords = e..feature.getGeometry().transform('EPSG:3857', 'EPSG:4326').getCoordinates()
}
this.olmap.addInteraction(draw)
I have to store the transformed coordinates in DB, but solution #2 does not maintain the visibility of drawn poloygon.
In case of solution #1, it does not gives the required formated coordinates, if I try to transform them later using
transform(coords, 'EPSG:3857', 'EPSG:4326')
it does not return formated coordinates.
please guide me where i am wrong to maintain the visibility of polygon and get the transformed coordinates.
You need to clone the geometry
let coords = e..feature.getGeometry().clone().transform('EPSG:3857', 'EPSG:4326').getCoordinates();
otherwise you wil move the feature somewhere close to point [0, 0] in view cooordinates

Is there a way make a reusable shape object?

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.

Categories

Resources