Subversion Repositories eFlore/Applications.cel

Rev

Rev 3871 | Blame | Compare with Previous | Last modification | View Log | RSS feed

import {NOMINATIM_OSM_URL,TbPlaces} from "./modules/Locality.js";

const tileLayers = {
    googleHybrid: [
        'https://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}',
        {
            attribution: '<a href="https://www.google.com" target="_blank">\xa9 Google Maps</a>',
            subdomains: ["mt0","mt1","mt2","mt3"],
            maxZoom: 20
        }
    ],
    osm: [
        'https://b.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png',
        //'https://osm.tela-botanica.org/tuiles/osmfr/{z}/{x}/{y}.png',
        {
            attribution: 'Data © <a href="http://osm.org/copyright">OpenStreetMap</a>',
            maxZoom: 18
        }
    ]
};
const defaultPosition = {
    lat: 47.0504,
    lng: 2.2347
};
const defaultCityZoom = 12;
const defaultZoom = 4;
const geometryFilterDefault = 'point';
const defaultMapIdAttr = 'tb-geolocation';
const markerImgBaseUrl = URL_BASE +'js/tb-geoloc/img/';
const markerIcon = L.Icon.extend({
    options: {
        shadowUrl: markerImgBaseUrl +'marker-shadow.png',
        iconAnchor: new L.Point(12, 40),//correctly replaces the dot of the pointer
        iconSize: new L.Point(24,40),
        iconUrl: markerImgBaseUrl +'marker-icon.png',
    }
});
const defaultPolylineOptions = {
    shapeOptions: {
        weight: 5
    },
    showLength: true,
    repeatMode: false
};

const drawLocale = {
    edit: {
        handlers: {
            edit: {
                tooltip: {
                    subtext: 'Cliquer sur annuler pour supprimer les changements',
                    text: 'Déplacez les marqueurs pour modifier leur position'
                }
            },
            remove: {
                tooltip: {
                    text: 'cliquer sur une ligne pour supprimer'
                }
            }
        },
        toolbar: {
            actions: {
                cancel: {
                    text: 'Annuler',
                    title: 'Annuler'
                },
                clearAll: {
                    text: 'Effacer',
                    title: 'Tout effacer'
                },
                save: {
                    text: 'Sauvegarder',
                    title: 'Sauvegarder les changements'
                }
            },
            buttons: {
                edit: 'Editer',
                editDisabled: 'Rien à editer',
                remove: 'Supprimer',
                removeDisabled: 'Rien à supprimer'
            }
        }
    },
    draw : {
        toolbar: {
            actions: {
                text: 'Annuler',
                title: 'Annuler'
            },
            buttons: {
                polyline: 'Dessiner une ligne',
                marker: 'Pointer une position'
            },
            finish: {
                text: 'Terminer',
                title: 'Terminer'
            },
            undo: {
                text: 'Supprimer',
                title: 'Supprimer le dernier point'
            }
        },
        handlers: {
            polyline: {
                tooltip: {
                    start: 'Cliquer sur la carte pour placer le début de la ligne',
                    cont: 'Positionner le prochain point et cliquer',
                    end: 'Re-cliquer sur le dernier point pour finir la ligne'
                }
            },
            marker: {
                tooltip: {
                    start: 'Cliquer sur la carte pour placer le marqueur'
                }
            },
            rectangle: {tooltip: {}},
            simpleshape: {tooltip: {}},
            circle: {tooltip: {}},
            circlemarker: {tooltip: {}}
        }
    }
};

/***************************************************/

export function Geoloc() {
    this.map = {};
    this.coordinates = defaultPosition;
    this.geojson = {};
    this.editableLayers = {};
    this.defaultDrawControlOptions = {};
    this.drawControl = {};
    this.defaultEditOptions = false;
    this.layer = {};
};

Geoloc.prototype.init = function(suffix = '') {
    this.mapIdAttr = defaultMapIdAttr + suffix;
    this.geolocLabel = document.getElementById('geoloc-label' + suffix);
    this.initForm();
    this.initEvts();
};

Geoloc.prototype.initForm = function() {
    this.mapEl = document.getElementById(this.mapIdAttr);
    this.zoom = this.mapEl.dataset.zoom || defaultZoom;
    this.geometryFilter = this.mapEl.dataset.typeLocalisation || geometryFilterDefault;

    const formSuffix = this.mapEl.dataset.formSuffix;

    this.latitudeEl = document.getElementById('latitude' + formSuffix);
    this.longitudeEl = document.getElementById('longitude' + formSuffix);
};

Geoloc.prototype.initEvts = function() {
    this.initMap();
    this.initMapCoordinates();
    this.initSearchLocality();
};

Geoloc.prototype.initMap = function() {
    this.map = this.createLocationMap();

    // interactions with map
    this.map.on(L.Draw.Event.CREATED, evt => {
        this.layer = evt.layer;

        // created marker or polyline with drawControl
        // no more need this drawcontrol: remove
        // (polyline: another one will be added with only edit toolbar)
        this.map.removeControl(this.drawControl);

        if ('marker' === evt.layerType) {
            this.onMarkerCreated();
        } else if ('polyline' === evt.layerType) {
            this.handlePolylineEvents();
        }
    });

    this.map.on(L.Draw.Event.EDITED, evt => {
        const layers = evt.layers;

        this.map.removeLayer(this.layer);

        layers.eachLayer(layer => {
            this.layer = layer;
            this.handlePolylineEvents(false);
        });
    });

    this.map.on(L.Draw.Event.DELETED, evt => {
        this.reSetDrawControl();
    });
};

Geoloc.prototype.initMapCoordinates = function() {
    if (!!this.latitudeEl.value && !!this.longitudeEl.value) {
        this.setMapCoordinates({
            'lat': this.latitudeEl.value,
            'lng': this.longitudeEl.value
        });
    }
    this.onCoordinates();
};

Geoloc.prototype.reSetDrawControl = function() {
    this.map.removeLayer(this.layer);
    this.map.removeControl(this.drawControl);
    this.setDrawControl(this.map);
};

Geoloc.prototype.createLocationMap = function() {
    const lthis = this,
        map = L.map(this.mapIdAttr, {zoomControl: true, gestureHandling: true}).setView([this.coordinates.lat, this.coordinates.lng], this.zoom),
        tileLayer = this.mapEl.dataset.layer || 'osm';

    L.tileLayer(...tileLayers[tileLayer]).addTo(map);

    this.editableLayers = new L.FeatureGroup();
    map.addLayer(this.editableLayers);

    this.setDrawControl(map);

    return map;
};

Geoloc.prototype.setDrawControl = function(map) {
    this.defaultDrawControlOptions = this.generateDrawControlOptions();
    this.drawControl = this.generateDrawControl(...Object.values(this.defaultDrawControlOptions));
    map.addControl(this.drawControl);
};

Geoloc.prototype.generateDrawControlOptions = function() {
    const options = {
        editOptions: false,
        markerOptions: false,
        polylineOptions: false
    };

    this.defaultEditOptions = {
        featureGroup: this.editableLayers,// REQUIRED!!
        remove: true
    }

    switch (this.geometryFilter) {
        case 'point':
            options.markerOptions = {
                icon: new markerIcon(),
                repeatMode: false
            };
            break;
        case 'rue':
            options.polylineOptions = defaultPolylineOptions;
            break;
        default:
            break;
    }

    return options;
};

Geoloc.prototype.generateDrawControl = function(
    editOptions,
    markerOptions = false,
    polylineOptions = false
) {
    L.drawLocal = drawLocale;

    return new L.Control.Draw({
        position: 'topleft',
        draw: {
            polyline: polylineOptions,
            polygon: false,
            circle: false,
            rectangle: false,
            marker: markerOptions,
            circlemarker: false
        },
        edit: editOptions
    });
};

Geoloc.prototype.onMarkerCreated = function() {
    const coordinates = this.layer.getLatLng();

    this.handleNewLocation(coordinates);
};

Geoloc.prototype.handleMarkerEvents = function() {
    if(this.map) {
        const lthis = this;
        // move marker on click
        $(this.map).off('click').on('click', function(evt) {
            lthis.handleNewLocation(evt.originalEvent.latlng);
        });
        // move marker on drag
        $(this.map.marker).off('dragend').on('dragend', function() {
            lthis.handleNewLocation(lthis.map.marker.getLatLng());
        });
    }
};

Geoloc.prototype.handlePolylineEvents = function(requiresNewEditDrawControl = true) {
    const latLngs = this.layer.getLatLngs(),
        polyline = this.formatPolyline(latLngs),
        coordinates = this.layer.getBounds().getCenter();


    if (requiresNewEditDrawControl) {
        this.drawControl = this.generateDrawControl(this.defaultEditOptions);
        this.map.addControl(this.drawControl);
    }

    // make polyline removable
    this.editableLayers.addLayer(L.polyline(latLngs));

    this.map.addLayer(this.layer);
    this.handleNewLocation(coordinates, polyline);
};

/**
 * triggers location event
 * @param coordinates.
 * @param coordinates.lat latitude.
 * @param coordinates.lng longitude.
 * @param {array||null} polyline array of coordinates object
 */
Geoloc.prototype.handleNewLocation = function (coordinates, polyline = []) {
    coordinates = this.formatCoordinates(coordinates);

    if(!!coordinates && !!coordinates.lat && coordinates.lng) {
        this.zoom = 20;
        this.setMapCoordinates(coordinates);

        if('point' === this.geometryFilter) {
            this.createDraggableMarker();
            this.handleMarkerEvents();
        }

        if (
            ('rue' === this.geometryFilter && 0 < polyline.length) ||
            ('point' === this.geometryFilter && 0 === polyline.length)
        ) {
            this.getLocationInfo(coordinates, polyline);
        }
    }
};

Geoloc.prototype.formatCoordinates = function (coordinates) {
    coordinates.lat = Number.parseFloat(coordinates.lat);
    coordinates.lng = Number.parseFloat(coordinates.lng);

    if(Number.isNaN(coordinates.lat) || Number.isNaN(coordinates.lng)) {
        return null;
    }

    return coordinates;
};

Geoloc.prototype.formatPolyline = function (latLngs) {
    const polyline = [];

    latLngs.forEach(coordinates => polyline.push(Object.values(coordinates).reverse()));

    return polyline;
};

Geoloc.prototype.setMapCoordinates = function (coordinates) {
    this.coordinates = coordinates;
    // updates map
    this.map.setView(coordinates,this.zoom);
};

Geoloc.prototype.createDraggableMarker = function() {
    if (undefined === this.map.marker) {
        // after many attempts, did not manage
        // to make marker from draw control draggable
        // solution: replace it
        if(this.layer) {
            this.map.removeLayer(this.layer);
        }
        this.map.marker = new L.Marker(this.coordinates, {
            draggable: true,
            icon: new markerIcon()
        });
        this.layer = this.map.marker;
        this.map.addLayer(this.map.marker);
        if(this.drawControl) {
            this.map.removeControl(this.drawControl);
        }
    }

    this.map.marker.setLatLng(this.coordinates, {draggable: 'true'});
};

Geoloc.prototype.getLocationInfo = function(coordinates, polyline) {
    const lthis = this,
        url = NOMINATIM_OSM_URL+'reverse',
        params = {
            'format': 'json',
            'lat': coordinates.lat,
            'lon': coordinates.lng
        };

    this.geolocLabel.classList.add('loading');

    $.ajax({
        method: "GET",
        url: url,
        data: params,
        success: function(locationData) {
            locationData.centroid = lthis.formatPointTypeCoordinates(coordinates);
            locationData.geojson = 'rue' === lthis.geometryFilter ? {type: 'LineString', coordinates: polyline} : locationData.centroid;
            lthis.loadGeolocation(coordinates,locationData);
        },
        error: (err) => {
            console.warn(err);
            lthis.geolocLabel.classList.remove('loading');
        }
    });
};

Geoloc.prototype.formatPointTypeCoordinates = function(coordinates) {
    return {type : 'Point', coordinates: Object.values(coordinates).reverse()};
};

Geoloc.prototype.loadGeolocation = function(coordinates,locationData) {
    const lthis = this,
        query = {
            'lat': coordinates.lat,
            'lon': coordinates.lng
        };

    $.ajax({
        method: "GET",
        url: URL_GEOLOC_SERVICE,
        data: query,
        success: function (geoLocationData) {
            lthis.triggerLocationEvent(
                lthis.formatLocationEventObject(locationData,geoLocationData)
            );
            lthis.geolocLabel.classList.remove('loading');
        },
        error:  (err) => {
            console.warn(err);
            lthis.geolocLabel.classList.remove('loading');
        }
    });
};

Geoloc.prototype.triggerLocationEvent = function(locationDataObject) {
    const locationEvent = new CustomEvent('location', {detail: locationDataObject});

    this.mapEl.dispatchEvent(locationEvent);
};

Geoloc.prototype.formatLocationEventObject = function(locationData,geoLocationData) {
    const detail = {
        centroid: locationData.centroid,
        geometry: locationData.geojson,
        elevation: geoLocationData.altitude,
        inseeData: {
            code: 'FR' === geoLocationData.code_pays ? geoLocationData.code_zone : '',
            nom: geoLocationData.nom
        },
        osmCountry: locationData.address.country,
        osmCountryCode: geoLocationData.code_pays,
        osmCounty: locationData.address.county,
        osmPostcode: locationData.address.postcode,
        locality: this.getLocalityFromData(locationData),
        locality: geoLocationData.nom,
        osmRoad: locationData.address.road,
        osmState: locationData.address.state,
        osmId: locationData.osm_id,
        osmPlaceId: locationData.place_id
    };

    return detail;
};

Geoloc.prototype.handleCoordinates = function() {
    if (!!this.latitudeEl.value && !!this.longitudeEl.value) {
        this.handleNewLocation({
            'lat': this.latitudeEl.value,
            'lng': this.longitudeEl.value
        });
    }
};

Geoloc.prototype.onCoordinates = function() {
    [this.latitudeEl,this.longitudeEl].forEach(coordinate =>
        coordinate.addEventListener('blur', function() {
            this.handleCoordinates();
        }.bind(this))
    );
};

Geoloc.prototype.initSearchLocality = function() {
    const placesZone = document.getElementById('tb-places-zone');

    if(placesZone) {
        placesZone.classList.remove('hidden');
        this.tbPlaces = new TbPlaces(this.tbPlacesCallback.bind(this));
        this.tbPlaces.init();
    }
};

Geoloc.prototype.tbPlacesCallback = function(localityData) {
    const locality = this.getLocalityFromData(localityData);

    if(!!locality) {
        const coordinates = this.formatCoordinates({
            'lat' : localityData.lat,
            'lng' : localityData.lon
        });

        if(!!coordinates && !!coordinates.lat && !!coordinates.lng) {
            this.zoom = 20;
            this.setMapCoordinates(coordinates);

            if('point' === this.geometryFilter) {
                this.map.removeControl(this.drawControl);
                this.createDraggableMarker();
                this.handleMarkerEvents();
            }

            localityData.centroid = this.formatPointTypeCoordinates(coordinates);
            if('rue' !== this.geometryFilter && 'Polygon' === localityData.geojson.type ) {
                localityData.geojson = localityData.centroid;
            }
            this.loadGeolocation(coordinates,localityData);
        }
    }
};

Geoloc.prototype.getLocalityFromData = function(localityData) {
    const addressData = localityData.address,

        locationNameType = ['village', 'city', 'locality', 'municipality', 'county', 'state'].find(locationNameType => addressData[locationNameType] !== undefined);
    if (!locationNameType) {
        return;
    }

    return addressData[locationNameType];
};

Geoloc.prototype.closeMap = function () {
    // reset map
    this.map = L.DomUtil.get(this.mapIdAttr);
    if (this.map != null) {
        this.map._leaflet_id = null;
    }
};