react-leaflet search plugin not working. Can anyone help me out with this - react-leaflet

I tried to implement react-leaflet search from https://github.com/tumerorkun/react-leaflet-search but, it didn't worked.
following is my code (without including react-leaflet search). Can anyone help me with it.
Hi, I tried to implement react-leaflet search from https://github.com/tumerorkun/react-leaflet-search but, it didn't worked.
following is my code (without including react-leaflet search). Can anyone help me with it.
import React from "react";
import L from "leaflet";
import 'leaflet-timedimension/dist/leaflet.timedimension.src';
import omnivore from '#mapbox/leaflet-omnivore';
import { Map, Marker, Popup } from 'react-leaflet';
import { ReactLeafletSearch } from 'react-leaflet-search';
// import * as countries from './data/map.geojson';
const config = {};
config.default = {
center: [51.505,-0.09],
zoomControl: true,
zoom: 4,
maxZoom: 19,
minZoom: 11,
scrollWheel: false,
legends: true,
infoControl: true,
attributionControl: true
};
config.tdOptions = {
timeInterval: "2018-09-30/2018-10-30",
period: "PT1H",
};
// ===== Example data sources
config.wmsOne = {
url: "https://demo.boundlessgeo.com/geoserver/ows",
options: { layers: 'nasa:bluemarble', transparent: true },
attribution: "&copy <a href="http://osm.org/copyright">CHANGE THIS</a> contributors",
};
config.wmsTwo = {
url: "https://demo.boundlessgeo.com/geoserver/ows",
options: { layers: 'ne:ne', transparent: true },
attribution: "&copy <a href="http://osm.org/copyright">CHANGE THIS</a> contributors",
};
config.osmLayer = {
url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
params: {
minZoom: 11,
attribution: "&copy <a href="http://osm.org/copyright">CHANGE THIS</a> contributors",
id: '',
accessToken: ''
}
};
// ===== END Example data sources
export default class LeafletMap extends React.Component {
constructor() {
super();
this.state = {
lat: config.default.center[0],
lng: config.default.center[1],
zoom: config.default.zoom,
toggleActive: true,
timeInterval: config.tdOptions.timeInterval,
period: config.tdOptions.period,
gpxLayer: null,
gpxTimeLayer: null,
};
this.initMap = this.initMap.bind(this);
this.initTimeDimension = this.initTimeDimension.bind(this);
this.onClick = this.onClick.bind(this); // Necessary?
}
// Executed when component is mounted Leaflet-React MAP component
componentDidMount() {
this.initMap();
// this.initTimeDimension(); // TODO: MAKE WORK GOOD!
}
initMap() {
// Usual Leaflet way, but we're using react-leaflet's <Map> component instead
/*this.map = new L.map("root", {
center: [this.state.lat, this.state.lng],
zoom: config.params.zoom,
timeDimension: true
});*/
console.log("### Initializing Leaflet map");
// Tile layer(s)
const tileLayerA =
L.tileLayer.wms(config.wmsOne.url, config.wmsOne.options);
const tileLayerB =
L.tileLayer.wms(config.wmsTwo.url, config.wmsTwo.options);
const tileLayerC = L.tileLayer.wms(config.osmLayer.url)
.addTo(this.map.leafletElement);
// const countriesLayer = L.geoJson(countries, {});
// Add controls for toggling layers
L.control.layers({
"OSM Layer": tileLayerC,
"Layer Two": tileLayerA,
"Layer One": tileLayerB
}).addTo(this.map.leafletElement);
}
// TODO: Make work!
initTimeDimension() {
console.log("### Init Leaflet TimeDimension");
let gpxTl = L.timeDimension.layer.geoJson(this.state.gpxLayer, {
updateTimeDimension: true,
addlastPoint: true,
waitForReady: true
});
this.setState({gpxLayer: omnivore.gpx('public/running_mallorca.gpx')});
this.setState({gpxTimeLayer: gpxTl});
// TimeDimension layer(s)
const td = new L.timeDimension({
period: "PT5M",
});
this.map.timeDimension = td;
//Player -> Component to animate a map with a TimeDimension, changing the time periodically.
let player = new L.TimeDimension.Player({
transitionTime: 100,
loop: false,
startOver:true
}, td);
L.control.timeDimension({
player: player,
}).addTo(this.map.leafletElement);
// Add timedimension from GPX data
new L.timeDimension.layer.geoJson(this.state.tdData, {
updateTimeDimension: true,
addlastPoint: true,
waitForReady: true
}).addTo(this.map.leafletElement);
}
onClick = () => {
this.setState({
toggleActive: !this.state.toggleActive,
});
console.log('CLICKED MAP');
// this.map.leafletElement.fitBounds(this.state.tdData);
};
render() {
const position = [this.state.lat, this.state.lng];
const timeDimensionOptions = {
timeInterval: this.state.timeInterval,
period: this.state.period
};
return (
<React.StrictMode>
<Map
center={position}
zoom={this.state.zoom}
onClick={this.onClick}
timeDimension={true}
timeDimensionOptions={timeDimensionOptions}
timeDimensionControl={true}
ref={(ref) => { this.map = ref; }}>
{/*<TileLayer
attribution={config.wmsOne.attribution}
url={config.wmsOne.url}
/>*/}
{/*<WMSTileLayer
layers={this.state.toggleActive ? 'nasa:bluemarble' : 'ne:ne'}
url="https://demo.boundlessgeo.com/geoserver/ows" />*/}
<Marker position={position}>
<Popup>
A pretty CSS3 popup (react-leaflet component). <br />Easily customizable.
</Popup>
</Marker>
</Map>
</React.StrictMode>
);
} // END of render()
}

Use this :
import { SearchControl } from 'react-leaflet-search';
It works, although it's not on the Documentation

Can you post a jsfiddle or codepen demonstrating the problem?
Also... incidentally the way you are adding controls and layers to the map is not recommended by react-leaflet.
new L.timeDimension.layer.geoJson(this.state.tdData, {...}).addTo(this.map.leafletElement);
is imperative. Prefer declarative rendering like
import { Map, TileLayer, GeoJSON } from 'react-leaflet';
<Map>
<TileLayer>
<GeoJSON>
</Map>

Related

How to render a Drawing manager in a Map or a polygon using "#react-google-maps/api"

I've been trying to render a Map using #react-google-maps/api": "^2.12.1 in my react-hook-form": "^7.33.1. The requirement was to show the map onClick of the input, and the user should draw a polygon on the Map using Drawing Manager.
This is my code, I've been trying to get this working from past two days and I'm nowhere near to the solution. I've gone through stack overflow questions but most of the answers were outdated or I couldn't find anything since most of the people are using react-google-maps where it has been completely redesigned to #react-google-maps/api": "^2.12.1.
The weird thing is the exact code is working in codesandbox. I don't know what I'm
doing wrong here.
import React from "react";
import ReactDOM from "react-dom";
import { LoadScript, GoogleMap, DrawingManager } from "#react-google-maps/api";
const containerStyle = {
width: "400px",
height: "400px",
};
const API_KEY = "";
export default function GoogleMaps() {
const [state, setState] = React.useState({
drawingMode: "polygon",
});
const noDraw = () => {
setState(function set(prevState) {
return Object.assign({}, prevState, {
drawingMode: "maker",
});
});
};
return (
<div className="App">
<LoadScript
id="script-loader"
googleMapsApiKey={API_KEY}
libraries={["drawing"]}
language="en"
region="us"
>
<GoogleMap
mapContainerClassName={containerStyle}
center={{
lat: 38.9065495,
lng: -77.0518192,
}}
zoom={10}
version="weekly"
>
<DrawingManager
drawingMode={state.drawingMode}
options={{
drawingControl: true,
drawingControlOptions: {
drawingModes: ["polygon"],
},
polygonOptions: {
fillColor: `#2196F3`,
strokeColor: `#2196F3`,
fillOpacity: 0.5,
strokeWeight: 2,
clickable: true,
editable: true,
draggable: true,
zIndex: 1,
},
}}
onPolygonComplete={(poly) => {
/*const polyArray = poly.getPath().getArray();
let paths = [];
polyArray.forEach(function(path) {
paths.push({ latitude: path.lat(), longitude: path.lng() });
});
console.log("onPolygonComplete", polyArray);*/
console.log("onPolygonComplete", poly);
noDraw();
}}
/*onOverlayComplete={poly => {
const polyArray = poly.getPath().getArray();
let paths = [];
polyArray.forEach(function(path) {
paths.push({ latitude: path.lat(), longitude: path.lng() });
});
console.log("onOverlayComplete", polyArray);
}}*/
/>
</GoogleMap>
</LoadScript>
</div>
);
}

I can make clickable country labels but get error when I click anywhere else

I am trying to create a pop when the user clicks the country label. My code accomplishes this but if the user accidentally clicks anywhere else I get the error "TypeError: Cannot read property 'properties' of undefined". Any help would be appreciated. Thanks.
export default class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
lng: -32.87393,
lat: 43.6439,
zoom: 3,
mxZoom: 7,
};
this.mapContainer = React.createRef();
}
componentDidMount() {
const { lng, lat, zoom , mxZoom} = this.state;
const map = new mapboxgl.Map({
container: this.mapContainer.current,
style: 'mapbox://styles/chriswade112358/ckpipp5el0jky18qwzep8ppyl',
center: [lng, lat],
zoom: zoom,
minZoom: zoom,
maxZoom: mxZoom,
});
map.on('style.load', function() {
map.on('click', function(e) {
const features = map.queryRenderedFeatures(e.point, { layers: ['country-label'] });
new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML('you clicked here: <br/>' + features[0].properties.name)
.addTo(map);
});
});
}

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