vue-leaflet / Vue2Leaflet

Vue 2 components for Leaflet maps
https://vue2-leaflet.netlify.app
MIT License
1.95k stars 381 forks source link

include layer to LFeatureGroup #399

Closed hubertokf closed 4 years ago

hubertokf commented 5 years ago

How to add layer to a LFeatureGroup?

I have this html structure:

<l-feature-group ref="features">
          <l-marker v-for="(marker, index) in markers" :lat-lng="marker" :key="'marker'+index"></l-marker>
          <l-polyline v-for="(polyline,index) in polylines" :lat-lngs="polyline.latlngs" :color="polyline.color" :key="'polyline'+index"></l-polyline>
          <l-polygon v-for="(polygon,index) in polygons" @click="teste" :lat-lngs="polygon.latlngs" :color="polygon.color"  :key="'polygon'+index"></l-polygon>
 </l-feature-group>

I have to create a Feature group for edition, so how can I place those drawing to a feature group that i could edit using leaflet.draw

DonNicoJs commented 5 years ago

@hubertokf I am sorry but I did not understand what is the problem, can you share a bit more of the code please?

hubertokf commented 5 years ago

Ok, I'm starting to use Leaflet.Draw. To make it work, assuming that I need to edit layers (drawings, polygons), I need to create a L.FeatureGroup and add those layers to this FeatureGroup, as you can see on this code:

var drawnItems = new L.FeatureGroup();
     map.addLayer(drawnItems);

// this.markers is my list of markers, so I'm creating marker layes from it and adding to my FeatureGroup drawnItems
let markerLayer = this.markers.map(m => L.marker(m.latlngs, m.options));
for(let layer of markerLayer) {
    drawnItems.addLayer(layer); 
}

Assuming that I have a LFeatureGroup, and I can add those layers inside it, those layers should be added to that FeatureGroup, as you can see in this code:

<l-feature-group ref="drawnItems">
          <l-marker v-for="(marker, index) in markers" :lat-lng="marker" :key="'marker'+index"></l-marker>
          <l-polyline v-for="(polyline,index) in polylines" :lat-lngs="polyline.latlngs" :color="polyline.color" :key="'polyline'+index"></l-polyline>
          <l-polygon v-for="(polygon,index) in polygons" @click="teste" :lat-lngs="polygon.latlngs" :color="polygon.color"  :key="'polygon'+index"></l-polygon>
        </l-feature-group>

But those markers, polygons etc, do not appear to be added to that feature group, because when I start edditing using Leaflet.Draw, those layers do not enter on edit mode. Actually I couldnt get that l-feature-group to configure on Leaflet.Draw.

Im trying to create a Draw component for Vue2Leaflet based on Leaflet.Draw. In my imagination, it should be like this:

<l-feature-group ref="drawnItems">
          <l-draw-toolbar position="topright"></l-draw-toolbar>
          <l-marker v-for="(marker, index) in markers" :lat-lng="marker" :key="'marker'+index"></l-marker>
          <l-polyline v-for="(polyline,index) in polylines" :lat-lngs="polyline.latlngs" :color="polyline.color" :key="'polyline'+index"></l-polyline>
          <l-polygon v-for="(polygon,index) in polygons" @click="teste" :lat-lngs="polygon.latlngs" :color="polygon.color"  :key="'polygon'+index"></l-polygon>
        </l-feature-group> 

Right now, what I am doing is to make all of it on map component as you can see on this code, but I want to put the Drawing part to a new component:

<template>
    <div class="dashboard">
    <l-map
      :key="componentKey"
      :zoom="zoom"
      :center="center"
      :options="mapOptions"
      :max-zoom="maxZoom"
      @update:center="centerUpdate"
      @update:zoom="zoomUpdate"
      @click="mapClick"
      ref="map"
    >
      <l-tile-layer
        :url="url"
        :options="{ maxZoom: 20}"
      />
      <l-tile-layer
        v-for="(orthomosaic, index) in computedOrthomosaics"
        :url="orthomosaic.tilesUrl"
        :tms="true"
        :key="orthomosaic.id"
        :options="{ maxZoom: 20, bounds: getPlanGeometry  }"
        :opacity="parseFloat(orthomosaic.opacity)"
      />
    </l-map>
  </div>
</template>

<script>
import { LMap, LImageOverlay, LTileLayer, LMarker, LPopup, LTooltip, LControlLayers, LPolyline, LPolygon, LFeatureGroup, LControl } from 'vue2-leaflet';
import 'leaflet/dist/leaflet.css';
import 'leaflet-draw/dist/leaflet.draw.css';
import 'leaflet-draw';
import 'leaflet-toolbar/dist/leaflet.toolbar.css'
import 'leaflet-toolbar/dist/leaflet.toolbar.js'

export default {
  name: "dashboard",
  data() {
    return {
      markers:[
        {
          latlngs: [47.313220, -1.319482],
          options:{}
        }
      ],
      polylines: [
        {
          latlngs: [
            [47.334852, -1.509485], 
            [47.342596, -1.328731], 
            [47.241487, -1.190568], 
            [47.234787, -1.358337]
          ],
          options: {
            color: 'green'
          }
        }                
      ],
      polygons: [
        {
          latlngs: [
            [-31.80544561213633, -52.417917251586914],
            [-31.809092668943386, -52.409076690673835],
            [-31.815073530409784, -52.416801452636726],
            [-31.81055145135166, -52.43001937866212],
            [-31.802527863029503, -52.4209213256836]
          ],
          options:{}
        }
      ],
      retangles: [],
      circles: [],
    };
  },
  created() {
  },
  methods: {
  },
  computed: {
  },
  components: {
    Sidenav,
    LMap,
    LTileLayer,
    LMarker,
    LPopup,
    LImageOverlay,
    LTooltip,
    LPolyline,
    LControlLayers,
    LFeatureGroup,
    LPolygon,
  },
  mounted () {

    this.$nextTick(() => {

      const map = this.$refs.map.mapObject;

      // Cria a layer de edição
      var drawnItems = new L.FeatureGroup();
      map.addLayer(drawnItems);      

      // Converte Polygons para Layers e adiciona para o drawnItems
      if(this.polygons){
        for(let layer of polygonLayer) {
          drawnItems.addLayer(layer); 
        }
      }

      // Converte Markers para Layers e adiciona para o drawnItems
      if(this.markers){
        let markerLayer = this.markers.map(m => L.marker(m.latlngs, m.options));
        for(let layer of markerLayer) {
          drawnItems.addLayer(layer); 
        }
      }

      // Converte Polyline para Layers e adiciona para o drawnItems
      if(this.polylines){
        let polylineLayer = this.polylines.map(p => L.polyline(p.latlngs, p.options));
        for(let layer of polylineLayer) {
          drawnItems.addLayer(layer); 
        }
      }

      // Converte Retangle para Layers e adiciona para o drawnItems
      if(this.retangles){
        let retangleLayer = this.retangles.map(r => L.retangle(r.latlngs, r.options));
        for(let layer of retangleLayer) {
          drawnItems.addLayer(layer); 
        }
      }

      // Converte Circle para Layers e adiciona para o drawnItems
      if(this.circles){
        let circleLayer = this.circles.map(r => L.circle(r.latlngs, r.options));
        for(let layer of circleLayer) {
          drawnItems.addLayer(layer); 
        }
      }

      var newThis = this

       map.on('draw:edited', function (e) {
        let geometrys = []
        let options = {}

        if (e.layerType == 'polygon' || e.layerType == 'rectangle' ) {
          geometrys = e.layer._latlngs[0].map(g => ({latitude: g.lat, longitude: g.lng }))
        } else if (e.layerType == 'polyline') {
          geometrys = e.layer._latlngs.map(g => ({latitude: g.lat, longitude: g.lng }))
        } else if (e.layerType == 'circle') {
          geometrys = [{latitude: e.layer._latlng.lat, longitude: e.layer._latlng.lng }]
          options.radius = e.layer._mRadius
        }

        let newAnnotation = {
          type: e.layerType,
          owner: newThis.$store.state.auth.user.id,
          plan: newThis.$store.getters.getCurrentPlan.id,
          geometrys: geometrys,
          options: options
        }

        newThis.$store.dispatch('editAnnotation', newAnnotation)
      })

      map.on('draw:created', function (e) {
        let geometrys = []
        let options = {}

        if (e.layerType == 'polygon' || e.layerType == 'rectangle' ) {
          geometrys = e.layer._latlngs[0].map(g => ({latitude: g.lat, longitude: g.lng }))
        } else if (e.layerType == 'polyline') {
          geometrys = e.layer._latlngs.map(g => ({latitude: g.lat, longitude: g.lng }))
        } else if (e.layerType == 'circle') {
          geometrys = [{latitude: e.layer._latlng.lat, longitude: e.layer._latlng.lng }]
          options.radius = e.layer._mRadius
        }

        let newAnnotation = {
          type: e.layerType,
          owner: newThis.$store.state.auth.user.id,
          plan: newThis.$store.getters.getCurrentPlan.id,
          geometrys: geometrys,
          options: options
        }

        newThis.$store.dispatch('postAnnotation', newAnnotation)
      });

      const drawControl = new L.Control.Draw({
        position: 'topright',
        draw: {
          polyline: {
            allowIntersection: false,
            showArea: true
          },
          polygon: true,
          rectangle: true,
          circle: true,
          marker: true,
          circlemarker: false
        },
        edit: {
          featureGroup: drawnItems
        }

      });

      map.addControl(drawControl);

    })
  },
};
</script>

<style lang="scss">
</style> 

About the Draw component, I started to make it from a parallel library, but i gave up. Now I'm going to make it based on original Leaflet.Draw.

Sorry about this long issue. I hope you could help me, as I could see, many coders want a draw component :)

hubertokf commented 5 years ago

Well, what I have to do is:

1- Create a Draw component receiving feature group 2- Create new Polygon, polyline, rectangle, circle and marker components. Each of this receive the feature group by props. On each one, I create a respective layer and add this to the feature group 3- Created feature group where map is and added all of this as map components

This is not beautiful and far from ideal, but worked. If you have suggestions of how to improove this to put it as a vue2leaflet plugin, I will be glad to modify it.

Next I present examples of how i did it. (Removed part of my own code to make it clear)

Example of Polygon:

<template>
  <div style="display: none;">
  </div>
</template>

<script>

export default {
  name: 'v-polygon',
  props: {
    latlngs: {
      type: Array,
    },
    options: {
      type: Object,
    },
    featureGroup: {
      type: Object,
    },
  },
  data() {
    return {
      layer: {}
    }
  },
  mounted() {

    this.$nextTick(() => {

      this.layer = L.polygon(this.latlngs, this.options)
      this.featureGroup.addLayer(this.layer)

    })
  },
};
</script>

Main example:

<template>
    <div class="dashboard">
    <div class="appMap">
      <l-map
        :key="componentKey"
        :zoom="zoom"
        :center="center"
        :options="mapOptions"
        :max-zoom="maxZoom"
        @update:center="centerUpdate"
        @update:zoom="zoomUpdate"
        @click="mapClick"
        ref="map"
      >
        <draw position="topright" :featureGroup="drawnItems"></draw>

        <v-polygon v-for="(polygon,index) in polygons" :id="polygon.id" :latlngs="polygon.latlngs" :options="polygon.options" :featureGroup="drawnItems" :key="'polygon-'+index"></v-polygon>

        <v-polyline v-for="(polyline,index) in polylines" :id="polyline.id" :latlngs="polyline.latlngs" :options="polyline.options" :featureGroup="drawnItems" :key="'polyline-'+index"></v-polyline>

        <v-rectangle v-for="(rectangle,index) in rectangles" :id="rectangle.id" :latlngs="rectangle.latlngs" :options="rectangle.options" :featureGroup="drawnItems" :key="'rectangle-'+index"></v-rectangle>

        <v-circle v-for="(circle,index) in circles" :latlngs="circle.latlngs" :options="circle.options" :featureGroup="drawnItems" :id="circle.id" :key="'circle-'+index"></v-circle>

        <v-marker v-for="(marker,index) in markers" :id="marker.id" :latlng="marker.latlngs" :options="marker.options" :featureGroup="drawnItems" :key="'marker-'+index"></v-marker>

        <l-tile-layer
          :url="url"
          :options="{ maxZoom: 20}"
        />
        <l-tile-layer
          v-for="(orthomosaic, index) in computedOrthomosaics"
          :url="orthomosaic.tilesUrl"
          :tms="true"
          :key="orthomosaic.id"
          :options="{ maxZoom: 20, bounds: getPlanGeometry  }"
          :opacity="parseFloat(orthomosaic.opacity)"
        />
      </l-map>
    </div>
    <sidenav></sidenav>

  </div>
</template>

<script>

export default {
  name: "dashboard",
  data() {
    return {
      markers:[
        {
          latlngs: [47.313220, -1.319482],
          options:{}
        }
      ],
      polylines: [
        {
          latlngs: [
            [47.334852, -1.509485], 
            [47.342596, -1.328731], 
            [47.241487, -1.190568], 
            [47.234787, -1.358337]
          ],
          options: {
            color: 'green'
          }
        }                
      ],
      polygons: [
        {
          latlngs: [
            [-31.80544561213633, -52.417917251586914],
            [-31.809092668943386, -52.409076690673835],
            [-31.815073530409784, -52.416801452636726],
            [-31.81055145135166, -52.43001937866212],
            [-31.802527863029503, -52.4209213256836]
          ],
          options:{
          }
        }
      ],
      rectangles: [],
      circles: [],
    };
  },
  created() {

    this.$root.$on('map-rerender', ()=>{
      this.componentKey++;
    });
    this.$root.$on('clear-annotations', ()=>{
      this.polygons = {}
      this.polylines = {}
      this.markers = {}
      this.rectangles = {}
      this.circles = {}
    });
    this.$root.$on('add-annotation', (annotation)=>{
      if (annotation.type == 'polygon') {
        this.polygons.push(annotation)
      }
      if (annotation.type == 'polyline') {
        this.polylines.push(annotation)
      }
      if (annotation.type == 'marker') {
        this.markers.push(annotation)
      }
      if (annotation.type == 'rectangle') {
        this.rectangles.push(annotation)
      }
      if (annotation.type == 'circle') {
        this.circles.push(annotation)
      }
    });
    this.$root.$on('edit-annotation', (annotation)=>{
      var polygonsIndex = this.rectangles.findIndex(annotation => annotation.id === annotation.id)
      var polylinesIndex = this.polylines.findIndex(annotation => annotation.id === annotation.id)
      var markersIndex = this.markers.findIndex(annotation => annotation.id === annotation.id)
      var rectanglesIndex = this.rectangles.findIndex(annotation => annotation.id === annotation.id)
      var circlesIndex = this.circles.findIndex(annotation => annotation.id === annotation.id)

      if (polygonsIndex != -1){
        this.polygons.splice(polygonsIndex, 1)
        this.polygons.push(annotation)
      }
      if (polylinesIndex != -1){
        this.polylines.splice(polylinesIndex, 1)
        this.polylines.push(annotation)
      }
      if (markersIndex != -1){
        this.markers.splice(markersIndex, 1)
        this.markers.push(annotation)
      }
      if (rectanglesIndex != -1){
        this.rectangles.splice(rectanglesIndex, 1)
        this.rectangles.push(annotation)
      }
      if (circlesIndex != -1){
        this.circles.splice(circlesIndex, 1)
        this.circles.push(annotation)
      }
    });

    this.$root.$on('delete-annotation', (annotationID)=>{
      var polygonsIndex = this.rectangles.findIndex(annotation => annotation.id === annotationID)
      var polylinesIndex = this.polylines.findIndex(annotation => annotation.id === annotationID)
      var markersIndex = this.markers.findIndex(annotation => annotation.id === annotationID)
      var rectanglessIndex = this.rectangles.findIndex(annotation => annotation.id === annotationID)
      var circlesIndex = this.circles.findIndex(annotation => annotation.id === annotationID)

      if (polygonsIndex != -1){
        this.polygons.splice(polygonsIndex, 1)
      }
      if (polylinesIndex != -1){
        this.polylines.splice(polylinesIndex, 1)
      }
      if (markersIndex != -1){
        this.markers.splice(markersIndex, 1)
      }
      if (rectanglessIndex != -1){
        this.rectangles.splice(rectanglessIndex, 1)
      }
      if (circlesIndex != -1){
        this.circles.splice(circlesIndex, 1)
      }
    });
  },
  mounted () {
    var map = this.$refs.map.mapObject
    this.drawnItems = new L.FeatureGroup();
    map.addLayer(this.drawnItems);      
  },
};
</script>

Draw example:

<template>
  <div style="display: none;">
  </div>
</template>

<script>

import 'leaflet-draw/dist/leaflet.draw.css';
import 'leaflet-draw';
import 'leaflet-toolbar/dist/leaflet.toolbar.css'
import 'leaflet-toolbar/dist/leaflet.toolbar.js'

export default {
  props: {
    featureGroup: {
      type: Object,
    },
    position: {
      type: String,
    },
  },
  mounted() {
    let parent = this.findRealParent(this)

    this.$nextTick(() => {
      const drawControl = new L.Control.Draw({
        position: this.position,
        draw: {
          polyline: {
            allowIntersection: false,
            showArea: true
          },
          polygon: true,
          rectangle: true,
          circle: true,
          marker: true,
          circlemarker: false
        },
        edit: {
          featureGroup: this.featureGroup
        }

      });

      parent.mapObject.addControl(drawControl);

      parent.mapObject.on('draw:created ', (e) => {
        const layer = e.layer,

        this.$root.$emit('add-annotation', layer)
      });

      parent.mapObject.on('draw:deleted', (e) => {
        let _this = this

        e.layers.eachLayer(function(layer){
          _this.$root.$emit('delete-annotation', layer)
        });
      })

      parent.mapObject.on('draw:edited', (e) => {
        let _this = this

        e.layers.eachLayer(function(layer){
          _this.$root.$emit('edit-annotation', annotation)
        });
      });
    })
  },
  methods: {
    findRealParent: (firstVueParent) => {
      let found = false;
      while (!found) {
        if (firstVueParent.mapObject === undefined) {
          firstVueParent = firstVueParent.$parent;
        } else {
          found = true;
        }
      }
      return firstVueParent;
    }
  },
};
</script>
DonNicoJs commented 5 years ago

@hubertokf sorry for the slow answer, it's nice that you found a workaround but maybe we can do better. If I have time I would like to start a vue-leaflet-draw plugin, would you like to collaborate there? or you can start it and I can jump on it. As you mentioned there are a lot of requests for it

hubertokf commented 5 years ago

Nice to hear! I already have initiated a repository named vue2-leaflet-draw-toolbar. As you suggested, I'm changing the name to vue2-leaflet-draw.

In the beginning I was using leaflet-draw-toolbar as base (just because of the tooltip functionality). But I'm ready to let it go and re-start it from leaflet-draw. Starting from reorganizing it there.

Sometimes I get lost in how I can organize leaflet things, and how to put code inside a vue component. But I hope you could guide me. :smile:

DonNicoJs commented 4 years ago

Closing due to inactivity, feel free to re-open

ryuchaehwa commented 4 years ago

Hey guys, I'm not here to reopen it, I'm here to just ask a question. I work at a solution company which makes a traffic sign management system. so we offer most services on top of the leaflet map. What I have to do is making a custom UI for our customers to add/copy/paste/cut/delete/group/and more functions to manage intersections' traffic lights(node=marker, link=polyline, vertex=circle, plus 8 more custom functions) with leaflet library. However, as you know the leaflet.draw is not for customizing functions at all. so I'm trying to make leaflet draw function as a component based one following leaflet-draw.js vanilla library.

I was actually just making a library by myself and planned not to use leaflet-draw.js as it seems have some issues with frameworks, but my boss made me to work with it. and the file that I've got from the former worker is over 6,000 lines(it's standalone one. can't use it with vue.js) and I started having a headache ...... Sorry for this TMI ;-P. Anyway please check below and help me out..

Error in nextTick: "TypeError: Cannot read property 'getLayers' of null"

leaflet-src.js

    addTo: function (map) {
        Control.prototype.addTo.call(this, map);
        // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
        return this._expandIfNotCollapsed();
    },
    onAdd: function (map) {
        var zoomName = 'leaflet-control-zoom',
            container = create$1('div', zoomName + ' leaflet-bar'),
            options = this.options;

        this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,
                zoomName + '-in',  container, this._zoomIn);
        this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
                zoomName + '-out', container, this._zoomOut);

        this._updateDisabled();
        map.on('zoomend zoomlevelschange', this._updateDisabled, this);

        return container;
    },

leaflet-draw.js

  addToolbar: function(map) {
    var container = L.DomUtil.create("div", "leaflet-draw-section"),
      buttonIndex = 0,
      buttonClassPrefix = this._toolbarClass || "",
      modeHandlers = this.getModeHandlers(map),
      i;

    this._toolbarContainer = L.DomUtil.create(
      "div",
      "leaflet-draw-toolbar leaflet-bar"
    );
    this._map = map;

    for (i = 0; i < modeHandlers.length; i++) {
      if (modeHandlers[i].enabled) {
        this._initModeHandler(
          modeHandlers[i].handler,
          this._toolbarContainer,
          buttonIndex++,
          buttonClassPrefix,
          modeHandlers[i].title
        );
      }
    }

    // if no buttons were added, do not add the toolbar
    if (!buttonIndex) {
      return;
    }

    // Save button index of the last button, -1 as we would have ++ after the last button
    this._lastButtonIndex = --buttonIndex;

    // Create empty actions part of the toolbar
    this._actionsContainer = L.DomUtil.create("ul", "leaflet-draw-actions");

    // Add draw and cancel containers to the control container
    container.appendChild(this._toolbarContainer);
    container.appendChild(this._actionsContainer);

    return container;
  },

Above codes are copied and pasted from the original one.

What I wrote:

mounted() {

     this.$nextTick(() => {
        let _this = this;
        let map = _this.$parent.$parent.$parent.$refs.map.mapObject;

        let nodeLayer =   L.featureGroup();
        map.addLayer(nodeLayer)

        let drawControl = new L.Control.Draw({
          draw: {
            polyline: false,
            polygon: false,
            rectangle: false,
            circle: false,
            node: {
              repeatMode: true,
              shapeOptions: {
                radius: 4,
                weight: 2,
                opacity: 0.5,
                // color: easyColorInfo.nodeDefColor,
                fillOpacity: 1
                // fillColor: easyColorInfo.nodeFillColor
              }
            },
            link: {
              allowIntersection: false,
              repeatMode: true,
              metric: true,
              feet: false, // When not metric, to use feet instead of yards for display.
              nautic: false, // When not metric, not feet use nautic mile for display
              shapeOptions: {
                weight: 4,
                opacity: 0.7
                // color: easyColorInfo.linkDefColor
              }
            },
            // tfdt: addon_tfdt,
            // tcdv: addon_tcdv,
            marker: false
          },
          edit: {
            popup: false
            // featureGroup: objectLayer,
            // clipboardGroup: clipboardLayer,
            // selectedGroup: featureSelect
          }
        });

        map.addControl(drawControl);
      });
}

====== leaflet: 1.5.1 laeflet-draw: 1.0.4 lefalet-draw-toolbar: 0.3.0-alpha.1 leaflet-toolbar: 0.4.0-alpha.2 vue2-leaflet: 2.1.1 vue2-leaflet-draw-toolbar: 0.1.1

DonNicoJs commented 4 years ago

@ryuchaehwa Happy to try to help, but we need two things: 1) a repro repo 2) could you please hop on Discord? The server is in the main README

ryuchaehwa commented 4 years ago

@DonNicoJs Hey! It's all good. I totally forgot to add a comment here. Sorry to bother you with this one. The issue came from 'var'. I've put draw, drag, handler's vanilla js to make them 1 vuejs wrapper but didn't think of refactoring the code a bit even I saw the variable names are same....... Totally my mistake here. I've re-declared all to 'let' and ta-da! It works just fine :)

Have a great day :)