Skip to content
Snippets Groups Projects
map-util.js 18.81 KiB
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import flip from '@turf/flip';
import axios from '@/axios-client.js';
import 'leaflet.vectorgrid';

let map;
let dictLayersToLeaflet = {};
var layerMVT;
let statusList = [
  {
    name: 'Brouillon',
    value: 'draft',
  },
  {
    name: 'Publié',
    value: 'published',
  },
  {
    name: 'Archivé',
    value: 'archived',
  },
  {
    name: 'En attente de publication',
    value: 'pending',
  },
];
L.TileLayer.BetterWMS = L.TileLayer.WMS.extend({

  onAdd: function (map) {
    // Triggered when the layer is added to a map.
    //   Register a click listener, then do all the upstream WMS things
    L.TileLayer.WMS.prototype.onAdd.call(this, map);
    map.on('click', this.getFeatureInfo, this);
  },

  onRemove: function (map) {
    // Triggered when the layer is removed from a map.
    //   Unregister a click listener, then do all the upstream WMS things
    L.TileLayer.WMS.prototype.onRemove.call(this, map);
    map.off('click', this.getFeatureInfo, this);
  },

  getFeatureInfo: function (evt) {
    if (this.wmsParams.basemapId != undefined) {
      const queryableLayerSelected = document.getElementById(`queryable-layers-selector-${this.wmsParams.basemapId}`).getElementsByClassName('selected')[0].textContent;
      if (queryableLayerSelected.trim() === this.wmsParams.title.trim()) {
        // Make an AJAX request to the server and hope for the best
        var params = this.getFeatureInfoUrl(evt.latlng);
        var showResults = L.Util.bind(this.showGetFeatureInfo, this);

        axios.get(
          window.proxy_url,
          {
            params: params,
          }
        ).then(response => {
          let data = response.data;
          var err = typeof data === 'object' ? null : data;
          if (data.features || err) {
            showResults(err, evt.latlng, data);
          }
        })
          .catch(error => {
            throw (error);
          }
          );

      }
    }
  },

  getFeatureInfoUrl: function (latlng) {
    // Construct a GetFeatureInfo request URL given a point
    var point = this._map.latLngToContainerPoint(latlng, this._map.getZoom());
    var size = this._map.getSize(),

      params = {
        url: this._url,
        request: 'GetFeatureInfo',
        service: 'WMS',
        // srs: this.wmsParams.srs,
        srs: 'EPSG:4326',
        // styles: this.wmsParams.styles,
        // transparent: this.wmsParams.transparent,
        version: this.wmsParams.version,
        // format: this.wmsParams.format,
        bbox: this._map.getBounds().toBBoxString(),
        height: size.y,
        width: size.x,
        layers: this.wmsParams.layers,
        query_layers: this.wmsParams.layers,
        info_format: 'application/json'
      };

    params[params.version === '1.3.0' ? 'i' : 'x'] = Math.floor(point.x);
    params[params.version === '1.3.0' ? 'j' : 'y'] = Math.floor(point.y);

    return params;
  },

  showGetFeatureInfo: function (err, latlng, data) {

    let content;

    if (err) {
      content = `
        <h4>${this.options.title}</h4>
        <p>Données de la couche inaccessibles</p>
      `;

      L.popup({ maxWidth: 800 })
        .setLatLng(latlng)
        .setContent(content)
        .openOn(this._map);
    } else {

      // Otherwise show the content in a popup
      let contentLines = [];
      let contentTitle;
      if (data.features.length > 0) {
        Object.entries(data.features[0].properties).forEach(entry => {
          const [key, value] = entry;
          if (key !== 'bbox') {
            contentLines.push(`<div>${key}: ${value}</div>`);
          }
        });
        contentTitle = `<h4>${this.options.title}</h4>`;
        content = contentTitle.concat(contentLines.join(''));

        L.popup({ maxWidth: 800 })
          .setLatLng(latlng)
          .setContent(content)
          .openOn(this._map);
      }
    }
  }

});
L.tileLayer.betterWms = function (url, options) {
  return new L.TileLayer.BetterWMS(url, options);
};

const mapUtil = {
  getMap: () => {
    return map;
  },

  createMap: function (el, options) {
    const {
      lat,
      lng,
      mapDefaultViewCenter,
      mapDefaultViewZoom,
      zoom,
      zoomControl = true,
    } = options;
    map = L.map(el, {
      maxZoom: 18,
      minZoom: 1,
      zoomControl: false,
    }).setView(
      [
        lat ? lat : mapDefaultViewCenter[0],
        lng ? lng : mapDefaultViewCenter[1],
      ],
      !zoom ? mapDefaultViewZoom : zoom
    );
    map.setMaxBounds(  [[-90,-180],   [90,180]]  );
    if (zoomControl) {
      L.control
        .zoom({
          zoomInTitle: 'Zoomer',
          zoomOutTitle: 'Dézoomer',
          position: 'topright',
        })
        .addTo(map);
    }

    L.control.scale().addTo(map);

    return map;
  },
  addGeocoders: function (configuration) {
    let geocoder;
    const geocoderLabel = configuration.SELECTED_GEOCODER.PROVIDER;
    if (geocoderLabel && L.Control.Geocoder) {
      const LIMIT_RESULTS = 5;
      if (
        geocoderLabel === configuration.GEOCODER_PROVIDERS.ADDOK
      ) {
        geocoder = L.Control.Geocoder.addok({ limit: LIMIT_RESULTS });
      } else if (
        geocoderLabel === configuration.GEOCODER_PROVIDERS.PHOTON
      ) {
        geocoder = L.Control.Geocoder.photon();
      } else if (
        geocoderLabel === configuration.GEOCODER_PROVIDERS.NOMINATIM
      ) {
        geocoder = L.Control.Geocoder.nominatim();
      }

      L.Control.geocoder({
        placeholder: 'Chercher une adresse...',
        geocoder: geocoder,
      }).addTo(map);
    }
  },
  addLayers: function (layers, serviceMap, optionsMap, schemaType) {
    if (layers) { //* if admin has defined basemaps for this project
      layers.forEach((layer) => {
        if (layer) {
          let options = layer.options;
          if (options) {
            options.noWrap = true;
            options.opacity = layer.opacity;

            if (layer.schema_type === 'wms') {
              let leafletLayer;
              if (layer.queryable) {
                options.title = layer.title;
                leafletLayer = L.tileLayer
                  .betterWms(layer.service, options)
                  .addTo(map);
              } else {
                leafletLayer = L.tileLayer
                  .wms(layer.service, options)
                  .addTo(map);
              }
              dictLayersToLeaflet[layer.id] = leafletLayer._leaflet_id;
            } else if (layer.schema_type === 'tms') {
              const leafletLayer = L.tileLayer(layer.service, options).addTo(map);
              dictLayersToLeaflet[layer.id] = leafletLayer._leaflet_id;
            }
          }
        }
      });
    } else { //* else when no basemaps defined
      optionsMap.noWrap = true;
      if (schemaType === 'wms') {
        L.tileLayer
          .wms(serviceMap, optionsMap)
          .addTo(map);
      } else {
        L.tileLayer(serviceMap, optionsMap).addTo(map);
      }
    }
  },

  // Remove the base layers (not the features)
  removeLayers: function () {
    map.eachLayer((leafLetlayer) => {
      if (
        Object.values(dictLayersToLeaflet).includes(leafLetlayer._leaflet_id)
      ) {
        map.removeLayer(leafLetlayer);
      }
    });
    dictLayersToLeaflet = {};
  },

  updateOpacity(layerId, opacity) {
    const internalLeafletLayerId = dictLayersToLeaflet[layerId];
    map.eachLayer((layer) => {
      if (layer._leaflet_id === internalLeafletLayerId) {
        layer.setOpacity(opacity);
      }
    });
  },

  updateOrder(layers) {
    // First remove existing layers undefined
    layers = layers.filter(function (x) {
      return x !== undefined;
    });
    // First remove existing layers
    map.eachLayer((leafLetlayer) => {
      layers.forEach((layerOptions) => {
        if (dictLayersToLeaflet[layerOptions.id] === leafLetlayer._leaflet_id) {
          map.removeLayer(leafLetlayer);
        }
      });
    });
    dictLayersToLeaflet = {};

    // Redraw the layers
    this.addLayers(layers);
  },

  retrieveFeatureColor: function (featureType, properties) {
    const colorsStyle = featureType.colors_style;
    if (featureType && colorsStyle && colorsStyle.custom_field_name) {
      const currentValue = properties[colorsStyle.custom_field_name];
      const colorStyle = colorsStyle.colors[currentValue];
      return colorStyle ? colorStyle : featureType.color;
    }
    return featureType.color;
  },

  addVectorTileLayer: function (url, projectSlug, featureTypes, formFilters) {
    layerMVT = L.vectorGrid.protobuf(url, {
      noWrap:true,
      vectorTileLayerStyles: {
        default: (properties) => {
          const featureType = featureTypes.find((x) => x.slug.split('-')[0] === '' + properties.feature_type_id);

          const color = this.retrieveFeatureColor(featureType, properties);
          const colorValue =
          color.value && color.value.length ?
            color.value : typeof color === 'string' && color.length ?
              color : '#000000';

          const hiddenStyle = ({
            radius: 0,
            fillOpacity: 0.5,
            weight: 0,
            fill: false,
            color: featureType.color,
          });

          const defaultStyle = {
            radius: 4,
            fillOpacity: 0.5,
            weight: 3,
            fill: true,
            color: colorValue,
          };

          // Filtre sur le feature type
          if (formFilters && formFilters.type.selected) {
            if (featureType.title !== formFilters.type.selected) {
              return hiddenStyle;
            }
          }
          // Filtre sur le statut
          if (formFilters && formFilters.status.selected.value) {
            if (properties.status !== formFilters.status.selected.value) {
              return hiddenStyle;
            }
          }
          // Filtre sur le titre
          if (formFilters && formFilters.title) {
            if (!properties.title.toLowerCase().includes(formFilters.title.toLowerCase())) {
              return hiddenStyle;
            }
          }
          return defaultStyle;
        },
      },
      // subdomains: "0123",
      // key: 'abcdefghi01234567890',
      interactive: true,
      maxNativeZoom: 18,
      getFeatureId: function (f) {
        return f.properties.id;
      }
    });
    layerMVT.on('click', (e) => {    // The .on method attaches an event handler
      const popupContent = this._createContentPopup(e.layer, featureTypes, projectSlug);
      L.popup()
        .setContent(popupContent)
        .setLatLng(e.latlng)
        .openOn(map);
    });
    layerMVT.addTo(map);
    window.layerMVT = layerMVT;
  },

  addFeatures: function (features, filter, addToMap = true, featureTypes, projectSlug) {
    const featureGroup = new L.FeatureGroup();
    features.forEach((feature) => {
      const featureProperties = feature.properties ? feature.properties : feature;
      const featureType = featureTypes
        .find((ft) => ft.slug === (featureProperties.feature_type.slug || featureProperties.feature_type));
      let filters = [];
      if (filter) {
        const typeCheck = filter.featureType && featureProperties.feature_type.slug === filter.featureType;
        const statusCheck = filter.featureStatus && featureProperties.status.value === filter.featureStatus;
        const titleCheck = filter.featureTitle && featureProperties.title.includes(filter.featureTitle);
        filters = [typeCheck, statusCheck, titleCheck];
      }

      if (
        !filter ||
        !Object.values(filter).some(val => val) ||
        Object.values(filter).some(val => val) &&
        filters.length && filters.every(val => val !== false)
      ) {
        const geomJSON = flip(feature.geometry || feature.geom);
        const popupContent = this._createContentPopup(feature, featureTypes, projectSlug);

        // Look for a custom field
        let customField;
        let customFieldOption;
        if (
          featureType.customfield_set &&
          Object.keys(featureProperties).some(el => featureType.customfield_set.map(e => e.name).includes(el))
        ) {
          customField = Object.keys(featureProperties)
            .filter(el => featureType.customfield_set.map(e => e.name).includes(el));
          customFieldOption = featureProperties[customField[0]];
        }
        let color = '#000000';
        if (feature.overideColor) {
          color = feature.overideColor;
        } else {
          color = this.retrieveFeatureColor(featureType, featureProperties) || featureProperties.color;
          if (color.value && color.value.length) {
            color = color.value;
          }
        }

        if (geomJSON.type === 'Point') {
          if (
            customFieldOption &&
            featureType.colors_style &&
            featureType.colors_style.value &&
            featureType.colors_style.value.icons &&
            !!Object.keys(featureType.colors_style.value.icons).length
          ) {
            if (
              featureType.colors_style.value.icons[customFieldOption] &&
              featureType.colors_style.value.icons[customFieldOption] !== 'circle'
            ) {
              const iconHTML = `
                <em
                  class="fas fa-${featureType.colors_style.value.icons[customFieldOption]} fa-lg"
                  style="color: ${color}"
                ></em>
              `;
              const customMapIcon = L.divIcon({
                html: iconHTML,
                iconSize: [20, 20],
                className: 'myDivIcon',
              });
              L.marker(geomJSON.coordinates, {
                icon: customMapIcon,
                color: color,
                zIndexOffset: 100
              })
                .bindPopup(popupContent)
                .addTo(featureGroup);
            } else {
              L.circleMarker(geomJSON.coordinates, {
                color: color,
                radius: 4,
                fillOpacity: 0.5,
                weight: 3,
              })
                .bindPopup(popupContent)
                .addTo(featureGroup);
            }
          } else {
            if (featureType.icon && featureType.icon !== 'circle') {
              const iconHTML = `
                <em
                  class="fas fa-${featureType.icon} fa-lg"
                  style="color: ${color}"
                ></em>
              `;
              const customMapIcon = L.divIcon({
                html: iconHTML,
                iconSize: [20, 20],
                className: 'myDivIcon',
              });
              L.marker(geomJSON.coordinates, {
                icon: customMapIcon,
                color: color,
                zIndexOffset: 100
              })
                .bindPopup(popupContent)
                .addTo(featureGroup);
            } else {
              L.circleMarker(geomJSON.coordinates, {
                color: color,
                radius: 4,
                fillOpacity: 0.5,
                weight: 3,
              })
                .bindPopup(popupContent)
                .addTo(featureGroup);
            }
          }
        } else if (geomJSON.type === 'LineString') {
          L.polyline(geomJSON.coordinates, {
            color: color,
            weight: 3,
          })
            .bindPopup(popupContent)
            .addTo(featureGroup);
        } else if (geomJSON.type === 'Polygon') {
          L.polygon(geomJSON.coordinates, {
            color: color,
            weight: 3,
            fillOpacity: 0.5,
          })
            .bindPopup(popupContent)
            .addTo(featureGroup);
        }
      }
    });

    if (map && addToMap) {
      map.addLayer(featureGroup);
    }

    return featureGroup;
  },
  addMapEventListener: function (eventName, callback) {
    map.on(eventName, callback);
  },

  _createContentPopup: function (feature, featureTypes, projectSlug) {
    const formatDate = (currentDatetime) => {
      let formattedDate = currentDatetime.getFullYear() + '-' + ('0' + (currentDatetime.getMonth() + 1)).slice(-2) + '-' + ('0' + currentDatetime.getDate()).slice(-2) + ' ' +
        ('0' + currentDatetime.getHours()).slice(-2) + ':' + ('0' + currentDatetime.getMinutes()).slice(-2);
      return formattedDate;
    };
    let featureType = feature.properties ? feature.properties.feature_type : feature.feature_type;
    let featureUrl = feature.feature_url;
    let status = feature.status;
    let featureTypeUrl = feature.feature_type_url;
    let dateMaj = feature.updated_on;
    if (feature.properties) {
      status = feature.properties.status;
      dateMaj = feature.properties.updated_on ? formatDate(new Date(feature.properties.updated_on)) : '<em>indisponible</em>';
      featureTypeUrl = feature.properties.feature_type_url;
      featureUrl = feature.properties.feature_url;
    }

    if (featureTypes && feature.properties) { // => VectorTile
      featureType = feature.properties.feature_type_id ?
        featureTypes.find((x) => x.slug.split('-')[0] === '' + feature.properties.feature_type_id) :
        featureTypes.find((fType) => fType.slug === feature.properties.feature_type); //* geojson
      status = statusList.find((x) => x.value === feature.properties.status.value).name;
      if (featureType) featureTypeUrl = `/geocontrib/projet/${projectSlug}/type-signalement/${featureType.slug}/`;
      featureUrl = `${featureTypeUrl}signalement/${feature.properties.feature_id}/`;
    } else {
      status = feature.properties ? feature.properties.status.label : feature.status.label;
    }

    //* adapt link url for shared-project restricted navigation
    if (window.location.pathname.includes('projet-partage')) {
      featureUrl = featureUrl.replace('projet', 'projet-partage');
      featureTypeUrl = featureTypeUrl.replace('projet', 'projet-partage');
    }
    let author = '';
    const creator = feature.properties ? feature.properties.creator : feature.creator;
    if (creator) {
      author = creator.full_name
        ? `<div>
          Auteur : ${creator.first_name} ${creator.last_name}
        </div>`
        : creator.username ? `<div>Auteur: ${creator.username}</div>` : '';
    }

    let title = feature.properties ? feature.properties.title : feature.title;
    if (featureUrl) {
      title = `<a href="${featureUrl}">${title}</a>`;
    } else {
      title = `<span>${title|| '<em>indisponible</em>'}</span>`;
    }

    if (featureTypeUrl) {
      `<a href="${featureTypeUrl}"> ${featureType.title || '<em>indisponible</em>'} </a>`;
    }

    return `
          <h4>
            <${featureUrl ? `a href="${featureUrl}"` : 'span'}>${title || '<em>indisponible</em>'}</${featureUrl ? 'a' : 'span'}>
          </h4>
          <div>
            Statut : ${status || '<em>indisponible</em>'}
          </div>
          <div>
            Type : <${featureTypeUrl ? `a href="${featureTypeUrl}"` : 'span'}> ${featureType.title || '<em>indisponible</em>'} </${featureTypeUrl ? 'a' : 'span'}>
          </div>
          <div>
            Dernière mise à jour : ${dateMaj || '<em>indisponible</em>'}
          </div>
          ${author}
        `;
  },
};

export { mapUtil };