Subversion Repositories eFlore/Applications.cel

Rev

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