Subversion Repositories eFlore/Applications.cel

Rev

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

Rev Author Line No. Line
3845 idir 1
import {NOMINATIM_OSM_URL,TbPlaces} from "./modules/Locality.js";
2
 
3
const tileLayers = {
4
    googleHybrid: [
5
        'https://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}',
6
        {
7
            attribution: '<a href="https://www.google.com" target="_blank">\xa9 Google Maps</a>',
8
            subdomains: ["mt0","mt1","mt2","mt3"],
9
            maxZoom: 20
10
        }
11
    ],
12
    osm: [
3955 delphine 13
        'https://b.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png',
14
        //'https://osm.tela-botanica.org/tuiles/osmfr/{z}/{x}/{y}.png',
3845 idir 15
        {
16
            attribution: 'Data © <a href="http://osm.org/copyright">OpenStreetMap</a>',
3864 delphine 17
            maxZoom: 18
3845 idir 18
        }
19
    ]
20
};
21
const defaultPosition = {
22
    lat: 47.0504,
23
    lng: 2.2347
24
};
25
const defaultCityZoom = 12;
26
const defaultZoom = 4;
27
const geometryFilterDefault = 'point';
28
const defaultMapIdAttr = 'tb-geolocation';
29
const markerImgBaseUrl = URL_BASE +'js/tb-geoloc/img/';
30
const markerIcon = L.Icon.extend({
31
    options: {
32
        shadowUrl: markerImgBaseUrl +'marker-shadow.png',
33
        iconAnchor: new L.Point(12, 40),//correctly replaces the dot of the pointer
34
        iconSize: new L.Point(24,40),
35
        iconUrl: markerImgBaseUrl +'marker-icon.png',
36
    }
37
});
38
const defaultPolylineOptions = {
39
    shapeOptions: {
40
        weight: 5
41
    },
42
    showLength: true,
43
    repeatMode: false
44
};
45
 
46
const drawLocale = {
47
    edit: {
48
        handlers: {
49
            edit: {
50
                tooltip: {
51
                    subtext: 'Cliquer sur annuler pour supprimer les changements',
52
                    text: 'Déplacez les marqueurs pour modifier leur position'
53
                }
54
            },
55
            remove: {
56
                tooltip: {
57
                    text: 'cliquer sur une ligne pour supprimer'
58
                }
59
            }
60
        },
61
        toolbar: {
62
            actions: {
63
                cancel: {
64
                    text: 'Annuler',
65
                    title: 'Annuler'
66
                },
67
                clearAll: {
68
                    text: 'Effacer',
69
                    title: 'Tout effacer'
70
                },
71
                save: {
72
                    text: 'Sauvegarder',
73
                    title: 'Sauvegarder les changements'
74
                }
75
            },
76
            buttons: {
77
                edit: 'Editer',
78
                editDisabled: 'Rien à editer',
79
                remove: 'Supprimer',
80
                removeDisabled: 'Rien à supprimer'
81
            }
82
        }
83
    },
84
    draw : {
85
        toolbar: {
86
            actions: {
87
                text: 'Annuler',
88
                title: 'Annuler'
89
            },
90
            buttons: {
91
                polyline: 'Dessiner une ligne',
92
                marker: 'Pointer une position'
93
            },
94
            finish: {
95
                text: 'Terminer',
96
                title: 'Terminer'
97
            },
98
            undo: {
99
                text: 'Supprimer',
100
                title: 'Supprimer le dernier point'
101
            }
102
        },
103
        handlers: {
104
            polyline: {
105
                tooltip: {
106
                    start: 'Cliquer sur la carte pour placer le début de la ligne',
107
                    cont: 'Positionner le prochain point et cliquer',
108
                    end: 'Re-cliquer sur le dernier point pour finir la ligne'
109
                }
110
            },
111
            marker: {
112
                tooltip: {
113
                    start: 'Cliquer sur la carte pour placer le marqueur'
114
                }
115
            },
116
            rectangle: {tooltip: {}},
117
            simpleshape: {tooltip: {}},
118
            circle: {tooltip: {}},
119
            circlemarker: {tooltip: {}}
120
        }
121
    }
122
};
123
 
124
/***************************************************/
125
 
126
export function Geoloc() {
127
    this.map = {};
128
    this.coordinates = defaultPosition;
129
    this.geojson = {};
130
    this.editableLayers = {};
131
    this.defaultDrawControlOptions = {};
132
    this.drawControl = {};
133
    this.defaultEditOptions = false;
134
    this.layer = {};
135
};
136
 
137
Geoloc.prototype.init = function(suffix = '') {
138
    this.mapIdAttr = defaultMapIdAttr + suffix;
139
    this.geolocLabel = document.getElementById('geoloc-label' + suffix);
140
    this.initForm();
141
    this.initEvts();
142
};
143
 
144
Geoloc.prototype.initForm = function() {
145
    this.mapEl = document.getElementById(this.mapIdAttr);
146
    this.zoom = this.mapEl.dataset.zoom || defaultZoom;
147
    this.geometryFilter = this.mapEl.dataset.typeLocalisation || geometryFilterDefault;
148
 
149
    const formSuffix = this.mapEl.dataset.formSuffix;
150
 
151
    this.latitudeEl = document.getElementById('latitude' + formSuffix);
152
    this.longitudeEl = document.getElementById('longitude' + formSuffix);
153
};
154
 
155
Geoloc.prototype.initEvts = function() {
156
    this.initMap();
3864 delphine 157
    this.initMapCoordinates();
3849 idir 158
    this.initSearchLocality();
3845 idir 159
};
160
 
161
Geoloc.prototype.initMap = function() {
162
    this.map = this.createLocationMap();
163
 
164
    // interactions with map
165
    this.map.on(L.Draw.Event.CREATED, evt => {
166
        this.layer = evt.layer;
167
 
168
        // created marker or polyline with drawControl
169
        // no more need this drawcontrol: remove
170
        // (polyline: another one will be added with only edit toolbar)
171
        this.map.removeControl(this.drawControl);
172
 
173
        if ('marker' === evt.layerType) {
174
            this.onMarkerCreated();
175
        } else if ('polyline' === evt.layerType) {
176
            this.handlePolylineEvents();
177
        }
178
    });
179
 
180
    this.map.on(L.Draw.Event.EDITED, evt => {
181
        const layers = evt.layers;
182
 
183
        this.map.removeLayer(this.layer);
184
 
185
        layers.eachLayer(layer => {
186
            this.layer = layer;
187
            this.handlePolylineEvents(false);
188
        });
189
    });
190
 
191
    this.map.on(L.Draw.Event.DELETED, evt => {
192
        this.reSetDrawControl();
193
    });
194
};
195
 
3864 delphine 196
Geoloc.prototype.initMapCoordinates = function() {
197
    if (!!this.latitudeEl.value && !!this.longitudeEl.value) {
198
        this.setMapCoordinates({
199
            'lat': this.latitudeEl.value,
200
            'lng': this.longitudeEl.value
201
        });
202
    }
203
    this.onCoordinates();
204
};
3845 idir 205
 
206
Geoloc.prototype.reSetDrawControl = function() {
207
    this.map.removeLayer(this.layer);
208
    this.map.removeControl(this.drawControl);
209
    this.setDrawControl(this.map);
210
};
211
 
212
Geoloc.prototype.createLocationMap = function() {
213
    const lthis = this,
214
        map = L.map(this.mapIdAttr, {zoomControl: true, gestureHandling: true}).setView([this.coordinates.lat, this.coordinates.lng], this.zoom),
215
        tileLayer = this.mapEl.dataset.layer || 'osm';
216
 
217
    L.tileLayer(...tileLayers[tileLayer]).addTo(map);
218
 
219
    this.editableLayers = new L.FeatureGroup();
220
    map.addLayer(this.editableLayers);
221
 
222
    this.setDrawControl(map);
223
 
224
    return map;
225
};
226
 
227
Geoloc.prototype.setDrawControl = function(map) {
228
    this.defaultDrawControlOptions = this.generateDrawControlOptions();
229
    this.drawControl = this.generateDrawControl(...Object.values(this.defaultDrawControlOptions));
230
    map.addControl(this.drawControl);
231
};
232
 
233
Geoloc.prototype.generateDrawControlOptions = function() {
234
    const options = {
235
        editOptions: false,
236
        markerOptions: false,
237
        polylineOptions: false
238
    };
239
 
240
    this.defaultEditOptions = {
241
        featureGroup: this.editableLayers,// REQUIRED!!
242
        remove: true
243
    }
244
 
245
    switch (this.geometryFilter) {
246
        case 'point':
247
            options.markerOptions = {
248
                icon: new markerIcon(),
249
                repeatMode: false
250
            };
251
            break;
252
        case 'rue':
253
            options.polylineOptions = defaultPolylineOptions;
254
            break;
255
        default:
256
            break;
257
    }
258
 
259
    return options;
260
};
261
 
262
Geoloc.prototype.generateDrawControl = function(
263
    editOptions,
264
    markerOptions = false,
265
    polylineOptions = false
266
) {
267
    L.drawLocal = drawLocale;
268
 
269
    return new L.Control.Draw({
270
        position: 'topleft',
271
        draw: {
272
            polyline: polylineOptions,
273
            polygon: false,
274
            circle: false,
275
            rectangle: false,
276
            marker: markerOptions,
277
            circlemarker: false
278
        },
279
        edit: editOptions
280
    });
281
};
282
 
283
Geoloc.prototype.onMarkerCreated = function() {
284
    const coordinates = this.layer.getLatLng();
285
 
286
    this.handleNewLocation(coordinates);
287
};
288
 
289
Geoloc.prototype.handleMarkerEvents = function() {
3847 idir 290
    if(this.map) {
291
        const lthis = this;
292
        // move marker on click
293
        $(this.map).off('click').on('click', function(evt) {
3849 idir 294
            lthis.handleNewLocation(evt.originalEvent.latlng);
3847 idir 295
        });
296
        // move marker on drag
297
        $(this.map.marker).off('dragend').on('dragend', function() {
298
            lthis.handleNewLocation(lthis.map.marker.getLatLng());
299
        });
300
    }
3845 idir 301
};
302
 
303
Geoloc.prototype.handlePolylineEvents = function(requiresNewEditDrawControl = true) {
304
    const latLngs = this.layer.getLatLngs(),
305
        polyline = this.formatPolyline(latLngs),
306
        coordinates = this.layer.getBounds().getCenter();
307
 
308
 
309
    if (requiresNewEditDrawControl) {
310
        this.drawControl = this.generateDrawControl(this.defaultEditOptions);
311
        this.map.addControl(this.drawControl);
312
    }
313
 
314
    // make polyline removable
315
    this.editableLayers.addLayer(L.polyline(latLngs));
316
 
317
    this.map.addLayer(this.layer);
318
    this.handleNewLocation(coordinates, polyline);
319
};
320
 
321
/**
322
 * triggers location event
323
 * @param coordinates.
324
 * @param coordinates.lat latitude.
325
 * @param coordinates.lng longitude.
326
 * @param {array||null} polyline array of coordinates object
327
 */
328
Geoloc.prototype.handleNewLocation = function (coordinates, polyline = []) {
329
    coordinates = this.formatCoordinates(coordinates);
330
 
331
    if(!!coordinates && !!coordinates.lat && coordinates.lng) {
3849 idir 332
        this.zoom = 20;
3848 idir 333
        this.setMapCoordinates(coordinates);
3849 idir 334
 
3848 idir 335
        if('point' === this.geometryFilter) {
336
            this.createDraggableMarker();
337
            this.handleMarkerEvents();
338
        }
3849 idir 339
 
340
        if (
341
            ('rue' === this.geometryFilter && 0 < polyline.length) ||
342
            ('point' === this.geometryFilter && 0 === polyline.length)
343
        ) {
344
            this.getLocationInfo(coordinates, polyline);
345
        }
3845 idir 346
    }
347
};
348
 
349
Geoloc.prototype.formatCoordinates = function (coordinates) {
350
    coordinates.lat = Number.parseFloat(coordinates.lat);
351
    coordinates.lng = Number.parseFloat(coordinates.lng);
352
 
353
    if(Number.isNaN(coordinates.lat) || Number.isNaN(coordinates.lng)) {
354
        return null;
355
    }
356
 
357
    return coordinates;
358
};
359
 
360
Geoloc.prototype.formatPolyline = function (latLngs) {
361
    const polyline = [];
362
 
3869 delphine 363
    latLngs.forEach(coordinates => polyline.push(Object.values(coordinates).reverse()));
3845 idir 364
 
365
    return polyline;
366
};
367
 
3848 idir 368
Geoloc.prototype.setMapCoordinates = function (coordinates) {
3845 idir 369
    this.coordinates = coordinates;
370
    // updates map
3849 idir 371
    this.map.setView(coordinates,this.zoom);
3845 idir 372
};
373
 
3848 idir 374
Geoloc.prototype.createDraggableMarker = function() {
3845 idir 375
    if (undefined === this.map.marker) {
376
        // after many attempts, did not manage
377
        // to make marker from draw control draggable
378
        // solution: replace it
379
        if(this.layer) {
380
            this.map.removeLayer(this.layer);
381
        }
382
        this.map.marker = new L.Marker(this.coordinates, {
383
            draggable: true,
384
            icon: new markerIcon()
385
        });
386
        this.layer = this.map.marker;
387
        this.map.addLayer(this.map.marker);
388
        if(this.drawControl) {
389
            this.map.removeControl(this.drawControl);
390
        }
391
    }
392
 
3848 idir 393
    this.map.marker.setLatLng(this.coordinates, {draggable: 'true'});
3845 idir 394
};
395
 
396
Geoloc.prototype.getLocationInfo = function(coordinates, polyline) {
397
    const lthis = this,
398
        url = NOMINATIM_OSM_URL+'reverse',
399
        params = {
400
            'format': 'json',
401
            'lat': coordinates.lat,
402
            'lon': coordinates.lng
403
        };
404
 
405
    this.geolocLabel.classList.add('loading');
406
 
407
    $.ajax({
408
        method: "GET",
409
        url: url,
410
        data: params,
411
        success: function(locationData) {
3869 delphine 412
            locationData.centroid = lthis.formatPointTypeCoordinates(coordinates);
413
            locationData.geojson = 'rue' === lthis.geometryFilter ? {type: 'LineString', coordinates: polyline} : locationData.centroid;
414
            lthis.loadGeolocation(coordinates,locationData);
3845 idir 415
        },
416
        error: (err) => {
417
            console.warn(err);
418
            lthis.geolocLabel.classList.remove('loading');
419
        }
420
    });
421
};
422
 
3869 delphine 423
Geoloc.prototype.formatPointTypeCoordinates = function(coordinates) {
424
    return {type : 'Point', coordinates: Object.values(coordinates).reverse()};
425
};
426
 
427
Geoloc.prototype.loadGeolocation = function(coordinates,locationData) {
3845 idir 428
    const lthis = this,
429
        query = {
430
            'lat': coordinates.lat,
431
            'lon': coordinates.lng
432
        };
433
 
434
    $.ajax({
435
        method: "GET",
436
        url: URL_GEOLOC_SERVICE,
437
        data: query,
438
        success: function (geoLocationData) {
439
            lthis.triggerLocationEvent(
3869 delphine 440
                lthis.formatLocationEventObject(locationData,geoLocationData)
3845 idir 441
            );
442
            lthis.geolocLabel.classList.remove('loading');
443
        },
444
        error:  (err) => {
445
            console.warn(err);
446
            lthis.geolocLabel.classList.remove('loading');
447
        }
448
    });
449
};
450
 
3869 delphine 451
Geoloc.prototype.triggerLocationEvent = function(locationDataObject) {
452
    const locationEvent = new CustomEvent('location', {detail: locationDataObject});
3845 idir 453
 
3869 delphine 454
    this.mapEl.dispatchEvent(locationEvent);
3845 idir 455
};
456
 
3869 delphine 457
Geoloc.prototype.formatLocationEventObject = function(locationData,geoLocationData) {
3845 idir 458
    const detail = {
3869 delphine 459
        centroid: locationData.centroid,
460
        geometry: locationData.geojson,
3845 idir 461
        elevation: geoLocationData.altitude,
462
        inseeData: {
3871 delphine 463
            code: 'FR' === geoLocationData.code_pays ? geoLocationData.code_zone : '',
3845 idir 464
            nom: geoLocationData.nom
465
        },
466
        osmCountry: locationData.address.country,
467
        osmCountryCode: geoLocationData.code_pays,
468
        osmCounty: locationData.address.county,
3869 delphine 469
        osmPostcode: locationData.address.postcode,
3845 idir 470
        locality: this.getLocalityFromData(locationData),
471
        locality: geoLocationData.nom,
472
        osmRoad: locationData.address.road,
473
        osmState: locationData.address.state,
474
        osmId: locationData.osm_id,
475
        osmPlaceId: locationData.place_id
476
    };
477
 
478
    return detail;
3869 delphine 479
};
3845 idir 480
 
481
Geoloc.prototype.handleCoordinates = function() {
482
    if (!!this.latitudeEl.value && !!this.longitudeEl.value) {
483
        this.handleNewLocation({
484
            'lat': this.latitudeEl.value,
485
            'lng': this.longitudeEl.value
486
        });
487
    }
488
};
489
 
490
Geoloc.prototype.onCoordinates = function() {
491
    [this.latitudeEl,this.longitudeEl].forEach(coordinate =>
492
        coordinate.addEventListener('blur', function() {
493
            this.handleCoordinates();
494
        }.bind(this))
495
    );
496
};
497
 
498
Geoloc.prototype.initSearchLocality = function() {
499
    const placesZone = document.getElementById('tb-places-zone');
500
 
501
    if(placesZone) {
502
        placesZone.classList.remove('hidden');
503
        this.tbPlaces = new TbPlaces(this.tbPlacesCallback.bind(this));
504
        this.tbPlaces.init();
505
    }
506
};
507
 
508
Geoloc.prototype.tbPlacesCallback = function(localityData) {
509
    const locality = this.getLocalityFromData(localityData);
510
 
511
    if(!!locality) {
3869 delphine 512
        const coordinates = this.formatCoordinates({
3845 idir 513
            'lat' : localityData.lat,
514
            'lng' : localityData.lon
3869 delphine 515
        });
3845 idir 516
 
3869 delphine 517
        if(!!coordinates && !!coordinates.lat && !!coordinates.lng) {
518
            this.zoom = 20;
519
            this.setMapCoordinates(coordinates);
520
 
521
            if('point' === this.geometryFilter) {
522
                this.map.removeControl(this.drawControl);
523
                this.createDraggableMarker();
524
                this.handleMarkerEvents();
525
            }
526
 
527
            localityData.centroid = this.formatPointTypeCoordinates(coordinates);
528
            if('rue' !== this.geometryFilter && 'Polygon' === localityData.geojson.type ) {
529
                localityData.geojson = localityData.centroid;
530
            }
531
            this.loadGeolocation(coordinates,localityData);
3849 idir 532
        }
3845 idir 533
    }
534
};
535
 
536
Geoloc.prototype.getLocalityFromData = function(localityData) {
537
    const addressData = localityData.address,
538
 
3871 delphine 539
        locationNameType = ['village', 'city', 'locality', 'municipality', 'county', 'state'].find(locationNameType => addressData[locationNameType] !== undefined);
3845 idir 540
    if (!locationNameType) {
541
        return;
542
    }
543
 
544
    return addressData[locationNameType];
545
};
546
 
547
Geoloc.prototype.closeMap = function () {
548
    // reset map
549
    this.map = L.DomUtil.get(this.mapIdAttr);
550
    if (this.map != null) {
551
        this.map._leaflet_id = null;
552
    }
3869 delphine 553
};