geoman-io / leaflet-geoman

🍂🗺️ The most powerful leaflet plugin for drawing and editing geometry layers
https://geoman.io
MIT License
2.21k stars 433 forks source link

integration with react-leaflet #417

Closed BjoernRave closed 5 years ago

BjoernRave commented 5 years ago

Hey, I was wondering if this library is compatible with react-leaflet or if somebody knows how to make it work with react (and maybe even next.js)

TravnikovDev commented 5 years ago

@BjoernRave at first you need import leaflet.pm files at root file of your project

import 'leaflet.pm/dist/leaflet.pm.css';
import 'leaflet.pm';

Then you need add ref to yours map component

<Map ref={(c) => { this.map = c }} ...

after this you can access map object in componentDidMount() and add toolbar and listeners to pm actions

componentDidMount() {
    if (typeof L !== 'undefined') {
      const map = this.map.leafletElement;
      map.pm.addControls({
        position: 'topleft',
        drawCircle: false,
      });
      layer.on('pm:create', e => {
         let newFeature = e.layer.toGeoJSON();
         dispatch({type: 'NEW_LAYER', payload: newFeature});
         layer.removeLayer(e.layer); // Remove temporary layer
     ...
BjoernRave commented 5 years ago

@TPABHuKOB first of all thanks alot for helping me, but when I try to import leaflet.pm I get the error ReferenceError: Element is not defined. I am using next.js by the way. I'm not sure what its refering to though and searching it was not very helpful...

codeofsumit commented 5 years ago

@BjoernRave I'm using leaflet.pm with nuxtJS, which is the next.js equivalent in the vue.js world. But I don't use react-leaflet nor vue-leaflet.

What's important is that leaflet and leaflet.pm are only initiated when the context of your application is the browser. In next.js, I assume your import of leaflet.pm is executed on the serverside. That might be handled by react-leaflet automatically for you.

Import leaflet.pm in the browser context only and it should work fine. Let me know how it goes.

(Will close the issue as it's not a problem to fix here but we can keep the discussion going to help you)

anthony-bernardo commented 4 years ago

I'm trying to integrate to react too, but I get TypeError: L.PM is undefined. Any idea ?

jeppe-style commented 4 years ago

In case someone still has this issue. Here is an example of how I integrated with react-leaflet (using TypeScript) by creating a custom component. The component adds circle drawing that I use for filtering in another component.

import React from 'react';
import { Map, MapProps } from 'react-leaflet';
import L from 'leaflet';

import '@geoman-io/leaflet-geoman-free';
import '@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css';

type PMDrawCircleEvent = { layer: L.Circle & { pm: { enable: () => void } } };
type PMEditCircleEvent = { target: L.Circle };

interface Props extends MapProps {
  onSelectionCircleAdded: (latLang: L.LatLng, radius: number) => void;
  onSelectionCircleMoved: (latLang: L.LatLng, radius: number) => void;
  onSelectionCircleRemoved: () => void;
}

const MapWithGeoman: React.FC<Props> = (props) => {
  const {
    children,
    onSelectionCircleAdded: onCircleAdded,
    onSelectionCircleMoved: onCircleMoved,
    onSelectionCircleRemoved: onCircleRemoved,
    ...mapProps
  } = props;

  const leafletMapRef = React.useRef<Map>(null);
  const filtersLayerRef = React.useRef<L.LayerGroup<L.Circle>>();

  React.useEffect(() => {
    if (leafletMapRef.current) {
      const mapElement = leafletMapRef.current.leafletElement;

      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      (mapElement as any).pm.addControls({
        drawMarker: false,
        drawCircleMarker: false,
        drawPolyline: false,
        drawRectangle: false,
        drawPolygon: false,
        editMode: false,
        dragMode: false,
        cutPolygon: false,
      });

      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      (mapElement as any).pm.setGlobalOptions({ pmIgnore: false });

      filtersLayerRef.current = L.layerGroup().addTo(mapElement);

      mapElement.on('pm:create', (e) => {
        // only show one selection circle
        filtersLayerRef.current?.clearLayers();

        if (
          e.layer &&
          e.layer.pm &&
          filtersLayerRef.current?.getLayers.length === 0
        ) {
          const circle = (e as unknown) as PMDrawCircleEvent;

          // enable editing of circle
          circle.layer.pm.enable();

          filtersLayerRef.current?.addLayer(circle.layer);

          onCircleAdded(circle.layer.getLatLng(), circle.layer.getRadius());

          circle.layer.on('pm:edit', (e) => {
            const event = (e as unknown) as PMEditCircleEvent;
            onCircleMoved(event.target.getLatLng(), event.target.getRadius());
          });
        }
      });

      mapElement.on('pm:remove', () => {
        onCircleRemoved();
      });
    }
  });

  return (
    <Map ref={leafletMapRef} {...mapProps}>
      {children}
    </Map>
  );
};

export default MapWithGeoman;
ddprajapati commented 4 years ago

@jeppe-style is there codesandbox available for playing with it. !!

jeppe-style commented 4 years ago

@ddprajapati I created a code sandbox here. Note that it at the moment it only works with v2.5. Filed a separate issue about that #640.

rohith231 commented 4 years ago

Hi this react geoman code is not working properly and i need an arc layer


import React, { Component } from "react";
import { Map, Marker, Popup, FeatureGroup,LayersControl,TileLayer,Polyline  } from "react-leaflet";

import ReactLeafletGoogleLayer from "react-leaflet-google-layer";
import { EditControl } from "react-leaflet-draw";
import L from "leaflet";
import "leaflet-routing-machine/dist/leaflet-routing-machine.css";
import "leaflet-routing-machine";

import { withLeaflet } from "react-leaflet"

import './centre.css'
import Routing from "./Routing";

import * as ELG from "esri-leaflet-geocoder";

const { BaseLayer } = LayersControl;

// import marker icons
delete L.Icon.Default.prototype._getIconUrl;

L.Icon.Default.mergeOptions({
  iconRetinaUrl:
    "https://unpkg.com/leaflet@1.4.0/dist/images/marker-icon-2x.png",
  iconUrl: "https://unpkg.com/leaflet@1.4.0/dist/images/marker-icon.png",
  shadowUrl: "https://unpkg.com/leaflet@1.4.0/dist/images/marker-shadow.png"
});

 class Path extends Component {
  constructor(props) {
    super(props);
    this.state = {
      lat: 17.3850 ,
      lng: 78.4867,
      zoom: 13,
      marker: [17.3850 ,78.4867]
    };
  }

  componentDidMount() {
    const map = this.leafletMap.leafletElement;
    const searchControl = new ELG.Geosearch().addTo(map);
    const results = new L.LayerGroup().addTo(map);
    const Polyline = L.Polyline.Arc([43.11667, 131.90000], [55.7522200, 37.6155600]).addTo(map);

    searchControl.on("results", function(data) {
      results.clearLayers();
      for (let i = data.results.length - 1; i >= 0; i--) {
        results.addLayer(L.marker(data.results[i].latlng));
      }
    });

  }

  terrain = "TERRAIN";
  key = "AIzaSyC0QH9aiCXuuRjJe4k5lzAM2bYl-MUhiPk";

  removeEverything = e => {
    console.log(e);
    const { lat, lng } = e.layer._latlng;
    this.setState({ marker: [lat, lng] });
    const { edit } = this.refs;
    var layerContainer = edit.leafletElement.options.edit.featureGroup;
    const layers = layerContainer._layers;
    const layer_ids = Object.keys(layers);
    layer_ids.splice(0, 1);
    layer_ids.forEach(id => {
      const layer = layers[id];
      layerContainer.removeLayer(layer);
    });
  };

  setPos = () => {
    this.setState({ marker: [17.3850, 78.4867] });
  };

  handleOnZoomed = e => {
    this.setState({ zoom: e.target._zoom });
  };

  render() {
    const position = [this.state.lat, this.state.lng];
    const center = [17.3850, 78.4867];

    return (
     <div>
        <pre>{JSON.stringify(this.state)}</pre>
        <button onClick={this.setPos}>remove</button>
        <Map
          center={this.state.marker}
          zoom={this.state.zoom}
          onzoomend={this.handleOnZoomed}
          ref={m => {
            this.leafletMap = m;
          }}
        >

          <Marker
            position={this.state.marker}
            defaultMarker={{ position: true }}
          >
          </Marker>

          <TileLayer
            attribution='&amp;copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
            url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
          />
          <FeatureGroup>
            <EditControl
              ref="edit"
              position="topright"
              onCreated={this.removeEverything}
              CircleMarker ="true"
              removalMode ="true"
              pinningOption ="true"
              snappingOption = "true"
              draw={{
                rectangle: true,
                polygon:true,
                CircleMarker:true,
                removalMode:true,
                pinningOption:true,
                snappingOption:true

              }}
            />
          </FeatureGroup>
          {/* <ReactLeafletGoogleLayer
            googleMapsLoaderConf={{ KEY: this.key }}
            type={"satellite"}
          /> */}

        <div className="pointer" />
        </Map>

        </div>
    );
  }
}

export default Path` 
@jeppe-style
Falke-Design commented 4 years ago

@ddprajapati I created a code sandbox here. Note that it at the moment it only works with v2.5. Filed a separate issue about that #640.

Fixed in Release: 2.7.0

kojhen commented 4 years ago

@ddprajapati I created a code sandbox here. Note that it at the moment it only works with v2.5. Filed a separate issue about that #640.

Fixed in Release: 2.7.0

Just downloaded the code from Sandbox and tried to start, but I'm getting this error:

Failed to compile.

./src/App.tsx
  Line 0:  Parsing error: Cannot read property 'map' of undefined

Could you please help?