Leaflet-control-geocoder: Cant get markers to render via Leaflet-control-geocoder reverse method - leaflet

I have a collection of latlng objects which also have elevation data. I'm mapping over this array and trying to pass that latlng object as a prop to this component i.e. <LeafletReverseGeocoding /> I created. What I want is to take that latlng I passed in and have the .reverse return the closet street near that latlng object. As some of those coordinates originally fall on buildings or non streets.
My hope is the elevation should be about the same to the original latlng.
In Marker component:
{topographyData.map((topoObject, index) => {
console.log("topoObject ", topoObject);
return (
<LeafletReverseGeocoding latlng={topoObject.latlng} key={index} />
);
})}
I'm following the docs here
Code for LeafletReverseGeocoding component:
import L from "leaflet";
import { useEffect } from "react";
import { useMap } from "react-leaflet";
import "leaflet-control-geocoder/dist/Control.Geocoder.js";
export function LeafletReverseGeocoding({ latlng }) {
const map = useMap();
console.log("latlng ", latlng);
useEffect(() => {
var geocoder = L.Control.Geocoder.nominatim();
let marker;
console.log("geocoder ", geocoder);
map.on("load", () => {
geocoder.reverse(
latlng,
map.options.crs.scale(map.getZoom()),
(results) => {
var r = results[0];
if (r) {
if (marker) {
marker
.setLatLng(r.center)
.setPopupContent(r.html || r.name)
.openPopup();
} else {
marker = L.marker(r.center)
.bindPopup(r.name)
.addTo(map)
.openPopup();
}
}
}
);
});
}, []);
return null;
}
Following this yields no markers. What am I missing.

Related

Custom transform control for geoman

I am trying to add a custom transform control to geoman, to do certain transformations with polylines and polygons. I see that on edit, geoman draws hint lines above vertices etc. I would like my tool to highlight polylines/polygons with the same type of hints. Below is the skeleton of my action:
const ConvertAction = L.Toolbar2.Action.extend({
options: {
toolbarIcon: {
html:
'<div class="icon-maps icon-convert" title="Convert point"></div>',
tooltip: 'Convert point'
}
},
addHooks: () => {
// draw polygon
// map.pm.enableDraw();
changeConvert();
}
});
function changeConvert() {
convert = true;
map.eachLayer(function (layer) {
if (layer.feature && layer.feature.geometry.type === 'Point') {
layer._icon.style['pointer-events'] = 'auto';
}
});
}
Is there an internal function or something that I could use to outline shapes? When I enable Edit layers tool already built into the geoman, shapes are outlined for me. How could I achieve this from my code without having to reimplement the entire thing?
Thus far, after quickly reviewing geoman code, I was able to come up with:
const ConvertAction = L.Toolbar2.Action.extend({
options: {
toolbarIcon: {
html:
'<div class="icon-maps icon-convert" title="Convert point"></div>',
tooltip: 'Convert point'
}
},
addHooks: () => {
// draw polygon
// map.pm.enableDraw();
if (!convert) changeConvert();
else disableConvert();
}
});
function changeConvert() {
convert = true;
map.eachLayer(function (layer) {
if (
layer?.feature?.geometry.type === 'Polygon' ||
layer?.feature?.geometry.type === 'LineString'
) {
const coords = layer.getLatLngs();
const markerGroup = new L.LayerGroup();
markerGroup._pmTempLayer = true;
const createMarker = (latlng) => {
const marker = new L.Marker(latlng, {
draggable: true,
icon: L.divIcon({ className: 'marker-icon' })
});
layer.options.pane =
(map.pm.globalOptions.panes &&
map.pm.globalOptions.panes.vertexPane) ||
'markerPane';
marker._pmTempLayer = true;
markerGroup.addLayer(marker);
return marker;
};
const handleRing = (coordsArr) => {
// if there is another coords ring, go a level deep and do this again
if (Array.isArray(coordsArr[0])) {
return coordsArr.map(handleRing, this);
}
// the marker array, it includes only the markers of vertexes (no middle markers)
const ringArr = coordsArr.map(createMarker);
return ringArr;
};
const markers = handleRing(coords);
map.addLayer(markerGroup);
}
});
}
function disableConvert() {
convert = false;
map.eachLayer(function (layer) {
if (
layer.dragging &&
layer.options?.draggable === true &&
layer._pmTempLayer === true
) {
console.log('temp layer:', layer);
map.removeLayer(layer);
}
});
update();
}
Seems like an excessive amount of code, and reimplementation [and probably not as good the geoman version as I don't fully understand geoman code by far] of existing functionality.
How do I simplify/fix this?

How to auto center my position after function getCurrentPosition with Leaflet?

I created a function that displays a marker on the map once geolocated, and the display does not center automatically.
What's the parameters for auto center my marker ?
I think this proble in on the getCenter.
import { useState} from 'react';
import { useMap } from "react-leaflet";
import L from "leaflet";
import icon from "./iconPosition";
export default function LocationMarker() {
const map = useMap();
let [currentPosition, setCurrentPosition] = useState([0, 0]);
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
setCurrentPosition([position.coords.latitude, position.coords.longitude]);
});
let latlng = currentPosition;
L.marker(latlng, { icon })
.addTo(map)
.bindPopup("Vous êtes ici")
.openPopup();
map.getCenter();
map.getZoom();
}
return (null)
};
You need to set the center and not to get the current center.
Replace
map.getCenter();
map.getZoom();
with
map.panTo(latlng)

Cannot access GeoJSON layer in react-leaflet

I am using React Leaflet to render Leaflet map and its GeoJSON component to render polygons. I am trying to implement dragging multiple polygons together at once, as a group.
I added Leaflet.Path.Drag library and tried to reuse this code. I am able to get transformation matrix which is in parent's state. If I want to apply this matrix to multiple polygons with _transform method, it doesn't work. I think the reason is that matrix is not applied to correct layers, but I have no idea how to fix this.
codesandbox.io
App.js
import React from "react";
import { MapContainer, GeoJSON, TileLayer } from "react-leaflet";
import { geoJson, latLngBounds } from "leaflet";
import "./styles.css";
import "leaflet/dist/leaflet.css";
import { GeoJsonContainer } from "./GeoJsonContainer";
const objects = [
{
polygon: {
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
type: "Polygon",
coordinates: [
[
[-104.98569488525392, 39.63431579014969],
[-104.98569488525392, 39.64165260123419],
[-104.97161865234376, 39.64165260123419],
[-104.97161865234376, 39.63431579014969]
]
]
}
}
]
}
},
{
polygon: {
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
type: "Polygon",
coordinates: [
[
[-105.02964019775392, 39.6206315500488],
[-105.02964019775392, 39.65685252543906],
[-104.99067306518556, 39.65685252543906],
[-104.99067306518556, 39.6206315500488]
]
]
}
}
]
}
}
];
const getPolygonPointFromBounds = (latLngBounds) => {
const center = latLngBounds.getCenter();
const latlngs = [];
latlngs.push(latLngBounds.getSouthWest()); //bottom left
latlngs.push({ lat: latLngBounds.getSouth(), lng: center.lng }); //bottom center
latlngs.push(latLngBounds.getSouthEast()); //bottom right
latlngs.push({ lat: center.lat, lng: latLngBounds.getEast() }); // center right
latlngs.push(latLngBounds.getNorthEast()); //top right
latlngs.push({
lat: latLngBounds.getNorth(),
lng: latLngBounds.getCenter().lng
}); //top center
latlngs.push(latLngBounds.getNorthWest()); //top left
latlngs.push({
lat: latLngBounds.getCenter().lat,
lng: latLngBounds.getWest()
}); //center left
return latlngs;
};
export default function App() {
const [matrix, setMatrix] = React.useState(null);
let newBounds = [];
let selectBoundingBox = [];
objects.forEach((building) => {
const polygonBounds = geoJson(building.polygon).getBounds();
newBounds = [...newBounds, polygonBounds];
});
const polygonPoints = getPolygonPointFromBounds(latLngBounds(newBounds));
const convertedData = polygonPoints.map((point) => [point.lng, point.lat]);
convertedData.push([polygonPoints[0].lng, polygonPoints[0].lat]);
selectBoundingBox = convertedData;
let selectBoxData = null;
if (selectBoundingBox) {
selectBoxData = {
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
type: "Polygon",
coordinates: [selectBoundingBox]
}
}
]
};
}
const handleFeature = (layer) => {
layer.makeDraggable();
layer.dragging.enable();
layer.on("drag", function (e) {
setMatrix(layer.dragging._matrix);
});
};
return (
<MapContainer center={[39.63563779557324, -104.99234676361085]} zoom={12}>
<TileLayer
attribution='&copy OpenStreetMap contributors'
url="https://{s}.tile.osm.org/{z}/{x}/{y}.png"
/>
{objects.map((object, i) => (
<GeoJsonContainer data={object} key={i} matrix={matrix} />
))}
<GeoJSON
data={selectBoxData}
style={() => ({
color: "green",
weight: 3,
opacity: 0.5
})}
draggable={true}
onEachFeature={(feature, layer) => handleFeature(layer)}
></GeoJSON>
</MapContainer>
);
}
GeoJsonContainer.js
import React from "react";
import { GeoJSON } from "react-leaflet";
require("leaflet-path-drag");
export const GeoJsonContainer = (props) => {
const geoJSONRef = React.useRef(null);
const layerRef = React.useRef(null);
React.useEffect(() => {
if (geoJSONRef?.current?._layers) {
console.log("mount layers", geoJSONRef.current?._layers);
}
}, []);
React.useEffect(() => {
if (geoJSONRef?.current._layers && props.matrix) {
console.log("transform layers", geoJSONRef.current._layers);
const key = Object.keys(geoJSONRef.current._layers)[0];
const geoJSONElementLayer = geoJSONRef.current._layers[key];
if (geoJSONElementLayer) {
console.log("geoJSONElementLayer", geoJSONElementLayer);
console.log("layerRef.current", layerRef.current);
geoJSONElementLayer._transform(props.matrix);
layerRef.current._transform(props.matrix);
}
}
}, [props.matrix]);
const handleFeature = (layer) => {
console.log("handleFeature layer", layer);
layerRef.current = layer;
layer.makeDraggable();
layer.dragging.enable();
};
return (
<GeoJSON
ref={geoJSONRef}
data={props.data.polygon}
style={() => ({
color: "#3388ff",
weight: 3,
opacity: 1
})}
dragging={true}
onEachFeature={(feature, layer) => handleFeature(layer)}
></GeoJSON>
);
};
Regarding
If I want to apply this matrix to multiple polygons with _transform
method, it doesn't work
This is the expected behavior in React since matrix prop needs to be immutable meaning a new array needs to be passed each time there is a change:
layer.on("drag", function (e) {
setMatrix([...layer.dragging._matrix]);
});
instead of:
layer.on("drag", function (e) {
setMatrix(layer.dragging._matrix);
});
This way GeoJsonContainer component should get re-rendered as expected.
Another matter concerns Leaflet.Path.Drag plugin, according to the referenced thread, in fact both drop and dropend events needs to be captured to properly apply transformation, so maybe instead of matrix prop, introduce a transform prop to keep matrix array and a flag to determine whether drop or is dropend event is triggered:
const handleFeature = (layer) => {
layer.makeDraggable();
layer.dragging.enable();
layer.on("drag", function (e) {
setTransform({matrix: layer.dragging._matrix, "end": false});
});
layer.on("dragend", function (e) {
setTransform({matrix: layer.dragging._matrix, "end": true});
});
};
and pass it into GeoJsonContainer component to apply geometry transform:
React.useEffect(() => {
if (props.transform) {
geoJSONRef.current.eachLayer((layer) => {
if (props.transform.end) dragDropTransform(layer);
else __dragTransform(layer);
});
}
}, [props.transform]);
where
function __dragTransform(layer) {
layer._transform(props.transform.matrix);
}
function dragDropTransform(layer) {
layer.dragging._transformPoints(props.transform.matrix);
layer._updatePath();
layer._project();
layer._transform(null);
}
Updated live demo
A possible improvement(s):
in the provided example two instances of JSON layers are instantiated, maybe to consider create a single instance of GeoJSON and apply a style per geometry?
Solution improvements proposal
In the provided example two layers are instantiated:
in App component
GeoJSON layer which renders a single outer geometry (polygon)
and in GeoJsonContainer component another one
GeoJSON layer which in turn renders a two inner geometries (polygons)
How about to merge both GeoJSON objects, something like this:
const dataSource = {...selectBoxData,...objects[0],...objects[1]}
and create a single layer instead:
<GeoJSON data={dataSource}></GeoJSON>
This way twice initialization for dragable layers could be avoided (refactoring of duplicated code)

Update google marker with watchposition in ionic

I am creating a map to track user position, i am using #geolocation plugin and showing map using:
<script src="https://maps.googleapis.com/maps/api/js?key=API_KEYscript>
Currently marker is being displayed and on watchposition marker is placing multiple times and isn’t removing last placed marker.
import { Component, ViewChild, ElementRef } from '#angular/core';
import { Geolocation, GeolocationOptions, Geoposition, PositionError } from '#ionic-native/geolocation';
declare var google;
#Component({
selector: 'app-tab1',
templateUrl: 'tab1.page.html',
styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
geolocation = Geolocation;
options: GeolocationOptions;
currentPos: Geoposition;
#ViewChild('map', { static: false }) mapElement: ElementRef;
map: any;
constructor() { }
ionViewDidEnter() {
this.getUserPosition();
}
getUserPosition() {
this.options = {
enableHighAccuracy: false
};
//get location
this.geolocation.getCurrentPosition(this.options).then((pos: Geoposition) => {
this.currentPos = pos;
console.log(pos);
//add map
let latLng = new google.maps.LatLng(pos.coords.latitude, pos.coords.longitude);
let mapOptions = {
center: latLng,
zoom: 18,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
this.map = new google.maps.Map(this.mapElement.nativeElement, mapOptions);
this.addMarker(latLng);
}, (err: PositionError) => {
console.log("error : " + err.message);
})
let subscription = this.geolocation.watchPosition().subscribe(position =>{
let userPosition = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
this.addMarker(userPosition);
})
}
addMarker(position) {
let marker = new google.maps.Marker({
map: this.map,
animation: google.maps.Animation.DROP,
position: position
});
let content = "<p>This is your current position !</p>";
let infoWindow = new google.maps.InfoWindow({
content: content
});
google.maps.event.addListener(marker, 'click', () => {
infoWindow.open(this.map, marker);
});
}
}
I want to remove last marker before placing another one.
Thank you.

React Leaflet: Updating coords of multiple markers in state

I can't seem to figure out how to properly update the coords of individual markers after storing them in the state. How it works currently is that when you click somewhere on the map, it adds a marker and stores its initial position in the state (in markerData) which is then displayed on the map through a map function. You can move the individual markers around but I'm having difficulty figuring out a possible solution to updating the specific markers position so that eventually I can send and store the marker information in the back end.
Here is my current code.
import React, { Component } from 'react';
import { ImageOverlay, Map, Marker, Popup } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import 'leaflet/dist/leaflet.js';
import L from 'leaflet';
delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
iconUrl: require('leaflet/dist/images/marker-icon.png'),
shadowUrl: require('leaflet/dist/images/marker-shadow.png')
});
export default class CustomMapExample extends Component {
constructor(props) {
super(props);
this.state = {
center: {
lat: 512,
lng: 1024,
},
zoom: 1,
draggable: true,
dimensions: [1024, 2048],
markerData: [],
};
}
toggleDraggable = () => {
this.setState({ draggable: !this.state.draggable })
}
addMarker = (event) => {
const {markerData} = this.state
const coords = event.latlng
markerData.push(coords)
this.setState({markerData})
}
updateMarker = (event) => {
console.log(event)
}
render () {
const boundOrigin = [0, 0];
const bounds = [boundOrigin, this.state.dimensions];
const position = [this.state.center.lat, this.state.center.lng]
return (
<div>
<Map
id="map"
crs={L.CRS.Simple}
minZoom={-1}
bounds={bounds}
center={position}
zoom={this.state.zoom}
onClick={this.addMarker}
>
<ImageOverlay
url='http://www.online-tabletop.com/wp-content/uploads/2017/01/tutoriala.jpg'
bounds={bounds}
/>
{this.state.markerData.map((element, index) =>
<Marker
key={index}
position={element}
draggable={this.state.draggable}
onDragend={this.updateMarker}
>
<Popup>
<span onClick={this.toggleDraggable}>
{this.state.draggable ? `Hello` : 'MARKER FIXED'}
</span>
</Popup>
</Marker>
)}
</Map>
</div>
);
}
}
Since Marker accepts options object as a second parameter, marker index could be introduced to reference which marker is getting updated:
<Marker
key={index}
marker_index={index}
position={element}
draggable={this.state.draggable}
onDragend={this.updateMarker}
/>
And then markerData state could be updated like this once the marker is dragged:
updateMarker = event => {
const latLng = event.target.getLatLng(); //get updated marker LatLng
const markerIndex = event.target.options.marker_index; //get marker index
//update
this.setState(prevState => {
const markerData = [...prevState.markerData];
markerData[markerIndex] = latLng;
return { markerData: markerData };
});
};
Demo