From c5716277996a608ea365da3f6657e5cd14de0206 Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Mon, 12 Jul 2021 16:27:39 +0200 Subject: [PATCH 01/30] feat(map): Add layerGroups by id_type Add layerGroups to the map for the user to be able to filter along the different cells (Department, cities, 10km, 1km) Added id_type to all queries to be able to filter along this property --- atlas/modeles/entities/vmObservations.py | 1 + .../vmObservationsMaillesRepository.py | 21 +++++--- static/mapGenerator.js | 52 ++++++++++++++++++- 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/atlas/modeles/entities/vmObservations.py b/atlas/modeles/entities/vmObservations.py index e84f63229..2151748ab 100644 --- a/atlas/modeles/entities/vmObservations.py +++ b/atlas/modeles/entities/vmObservations.py @@ -48,6 +48,7 @@ class VmObservationsMailles(Base): metadata, Column("id_observation", Integer, primary_key=True, unique=True), Column("id_maille", Integer), + Column("id_type", Integer), Column("the_geom", Geometry), Column("geojson_maille", String(1000)), Column("annee", String(1000)), diff --git a/atlas/modeles/repositories/vmObservationsMaillesRepository.py b/atlas/modeles/repositories/vmObservationsMaillesRepository.py index 801aec73c..45d8c114d 100644 --- a/atlas/modeles/repositories/vmObservationsMaillesRepository.py +++ b/atlas/modeles/repositories/vmObservationsMaillesRepository.py @@ -17,9 +17,10 @@ def getObservationsMaillesChilds(session, cd_ref, year_min=None, year_max=None): func.count(VmObservationsMailles.id_observation).label("nb_obs"), func.max(VmObservationsMailles.annee).label("last_observation"), VmObservationsMailles.id_maille, - VmObservationsMailles.geojson_maille, + VmObservationsMailles.id_type, + VmObservationsMailles.geojson_maille ) - .group_by(VmObservationsMailles.id_maille, VmObservationsMailles.geojson_maille) + .group_by(VmObservationsMailles.id_maille, VmObservationsMailles.geojson_maille, VmObservationsMailles.id_type) .filter( or_( VmObservationsMailles.cd_ref.in_(subquery), @@ -37,6 +38,7 @@ def getObservationsMaillesChilds(session, cd_ref, year_min=None, year_max=None): geometry=json.loads(o.geojson_maille), properties={ "id_maille": o.id_maille, + "id_type": o.id_type, "nb_observations": o.nb_obs, "last_observation": o.last_observation, }, @@ -73,6 +75,7 @@ def lastObservationsMailles(connection, mylimit, idPhoto): temp = { "id_observation": o.id_observation, "id_maille": o.id_maille, + "id_type": o.id_type, "cd_ref": o.cd_ref, "dateobs": str(o.dateobs), "altitude_retenue": o.altitude_retenue, @@ -101,11 +104,13 @@ def lastObservationsCommuneMaille(connection, mylimit, insee): ORDER BY obs.dateobs DESC LIMIT :thislimit ) - SELECT l.lb_nom, l.nom_vern, l.cd_ref, m.id_maille, m.geojson_maille + SELECT l.lb_nom, l.nom_vern, l.cd_ref, m.id_maille, m.id_type, + m.geojson_maille FROM atlas.t_mailles_territoire m JOIN last_obs l - ON st_intersects(l.l_geom, m.the_geom) - GROUP BY l.lb_nom, l.cd_ref, m.id_maille, l.nom_vern, m.geojson_maille + ON st_equals(l.l_geom, m.the_geom) + GROUP BY l.lb_nom, l.cd_ref, m.id_maille, m.id_type, l.nom_vern, + m.geojson_maille """ observations = connection.execute(text(sql), thisInsee=insee, thislimit=mylimit) obsList = list() @@ -119,6 +124,7 @@ def lastObservationsCommuneMaille(connection, mylimit, insee): "taxon": taxon, "geojson_maille": json.loads(o.geojson_maille), "id_maille": o.id_maille, + "id_type": o.id_type, } obsList.append(temp) return obsList @@ -128,13 +134,13 @@ def lastObservationsCommuneMaille(connection, mylimit, insee): def getObservationsTaxonCommuneMaille(connection, insee, cd_ref): sql = """ SELECT - o.cd_ref, t.id_maille, t.geojson_maille, + o.cd_ref, t.id_maille, t.id_type, t.geojson_maille, extract(YEAR FROM o.dateobs) as annee FROM atlas.vm_observations o JOIN atlas.vm_communes c ON ST_INTERSECTS(o.the_geom_point, c.the_geom) JOIN atlas.t_mailles_territoire t - ON ST_INTERSECTS(t.the_geom, o.the_geom_point) + ON ST_EQUALS(t.the_geom, o.the_geom_point) WHERE o.cd_ref = :thiscdref AND c.insee = :thisInsee ORDER BY id_maille """ @@ -143,6 +149,7 @@ def getObservationsTaxonCommuneMaille(connection, insee, cd_ref): for o in observations: temp = { "id_maille": o.id_maille, + "id_type": o.id_maille, "nb_observations": 1, "annee": o.annee, "geojson_maille": json.loads(o.geojson_maille), diff --git a/static/mapGenerator.js b/static/mapGenerator.js index 40ecbee3f..5d316386e 100644 --- a/static/mapGenerator.js +++ b/static/mapGenerator.js @@ -10,6 +10,18 @@ function generateMap() { baseMap = {}; baseMap[configuration.MAP.FIRST_MAP.tileName] = firstMapTile; + cities = L.layerGroup(); + department = L.layerGroup(); + tenCell = L.layerGroup(); + oneCell = L.layerGroup(); + + var overlays = { + "Communes": cities, + "Departement": department, + "10km²": tenCell, + "1km²": oneCell + } + var map = L.map("map", { crs: L.CRS.EPSG3857, center: configuration.MAP.LAT_LONG, @@ -21,6 +33,14 @@ function generateMap() { fullscreenControl: true }); + // Add layers + L.control.layers(null, overlays).addTo(map) + + // Activate layers + for (var key in overlays) { + map.addLayer(overlays[key]) + } + // Style of territory on map territoryStyle = { fill: false, @@ -99,7 +119,10 @@ function generateMap() { fullScreenButton.attr("data-original-title", "Plein écran"); $(".leaflet-control-fullscreen-button").removeAttr("title"); + return map; + + } //****** Fonction fiche espècce *********** @@ -124,10 +147,13 @@ function onEachFeaturePoint(feature, layer) { } else { layer.bindPopup(popupContent); } + + filterMaille(feature, layer) } // popup Maille function onEachFeatureMaille(feature, layer) { + popupContent = "Nombre d'observation(s): " + feature.properties.nb_observations + @@ -135,6 +161,8 @@ function onEachFeatureMaille(feature, layer) { feature.properties.last_observation + " "; layer.bindPopup(popupContent); + + filterMaille(feature, layer) } // Style maille @@ -237,6 +265,7 @@ function generateGeojsonMaille(observations, yearMin, yearMax) { function displayMailleLayerFicheEspece(observationsMaille) { myGeoJson = observationsMaille; + currentLayer = L.geoJson(myGeoJson, { onEachFeature: onEachFeatureMaille, style: styleMaille @@ -366,6 +395,7 @@ function displayMarkerLayerFicheEspece( /* *** Point ****/ function onEachFeaturePointLastObs(feature, layer) { + popupContent = "Espèce: " + feature.properties.taxon + @@ -498,12 +528,15 @@ function printEspece(tabEspece, tabCdRef) { } function onEachFeatureMailleLastObs(feature, layer) { + popupContent = "Espèces observées dans la maille: "; layer.bindPopup(popupContent); + + filterMaille(feature, layer) } function styleMailleLastObs() { @@ -515,6 +548,22 @@ function styleMailleLastObs() { }; } +function filterMaille(feature, layer) { + id = feature.properties.id_type; + if (id === 25 ) { + cities.addLayer(layer); + } + else if ( id === 26) { + department.addLayer(layer); + } + else if ( id === 27) { + tenCell.addLayer(layer); + } + else { + oneCell.addLayer(layer); + } +} + function generateGeoJsonMailleLastObs(observations) { var i = 0; myGeoJson = { type: "FeatureCollection", features: [] }; @@ -522,6 +571,7 @@ function generateGeoJsonMailleLastObs(observations) { geometry = observations[i].geojson_maille; idMaille = observations[i].id_maille; properties = { + id_type: observations[i].id_type, id_maille: idMaille, list_taxon: [observations[i].taxon], list_cdref: [observations[i].cd_ref], @@ -561,7 +611,7 @@ function displayMailleLayerLastObs(observations) { onEachFeature: onEachFeatureMailleLastObs, style: styleMailleLastObs }); - currentLayer.addTo(map); + // currentLayer.addTo(map); } // Legend From 56dac335191939c6ad6ca515839fe5f376d9dc14 Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Mon, 12 Jul 2021 18:16:59 +0200 Subject: [PATCH 02/30] feat(map): Zoom, snogylop and features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct zoom function to work on all "fiches" by adding a zoomMaille function Added snogylop to ficheCommune.html and invert: true in style of map Changed layerGroup by featureGroup to have bringToBack and bringToFront functions Add bringToFront and bringToBack to respectively the 1km² cell and the department because it was impossible to click on a city cell --- static/mapCommune.js | 4 ++-- static/mapGenerator.js | 44 ++++++++++++++++++++++++++----------- static/mapHome.js | 17 +++++++++----- templates/ficheCommune.html | 2 ++ 4 files changed, 47 insertions(+), 20 deletions(-) diff --git a/static/mapCommune.js b/static/mapCommune.js index c452754f4..1a39fcb89 100644 --- a/static/mapCommune.js +++ b/static/mapCommune.js @@ -3,7 +3,6 @@ var map = generateMap(); var legend = L.control({ position: "bottomright" }); // Current observation Layer: leaflet layer type var currentLayer; - // Current observation geoJson: type object var myGeoJson; @@ -16,7 +15,8 @@ var communeLayer = L.geoJson(communeGeoJson, { weight: 2, color: "black", dashArray: "3", - fillOpacity: 0 + fillOpacity: 0, + invert: true }; } }).addTo(map); diff --git a/static/mapGenerator.js b/static/mapGenerator.js index 5d316386e..7de0da5fd 100644 --- a/static/mapGenerator.js +++ b/static/mapGenerator.js @@ -10,14 +10,14 @@ function generateMap() { baseMap = {}; baseMap[configuration.MAP.FIRST_MAP.tileName] = firstMapTile; - cities = L.layerGroup(); - department = L.layerGroup(); - tenCell = L.layerGroup(); - oneCell = L.layerGroup(); + department = L.featureGroup(); + cities = L.featureGroup(); + tenCell = L.featureGroup(); + oneCell = L.featureGroup(); var overlays = { - "Communes": cities, "Departement": department, + "Communes": cities, "10km²": tenCell, "1km²": oneCell } @@ -147,8 +147,6 @@ function onEachFeaturePoint(feature, layer) { } else { layer.bindPopup(popupContent); } - - filterMaille(feature, layer) } // popup Maille @@ -162,7 +160,9 @@ function onEachFeatureMaille(feature, layer) { " "; layer.bindPopup(popupContent); - filterMaille(feature, layer) + filterMaille(feature, layer); + + zoomMaille(layer); } // Style maille @@ -193,6 +193,18 @@ function styleMaille(feature) { }; } +function zoomMaille(layer) { + layer.on('click', function(e){ + bounds = e.sourceTarget.feature.geometry.coordinates; + bounds = bounds.map(b => { + return b.map(c => { + return c.map(d => [d[1], d[0]]) + }) + }) + map.fitBounds(bounds); + }); +} + function generateLegendMaille() { legend.onAdd = function(map) { var div = L.DomUtil.create("div", "info legend"), @@ -536,7 +548,9 @@ function onEachFeatureMailleLastObs(feature, layer) { layer.bindPopup(popupContent); - filterMaille(feature, layer) + filterMaille(feature, layer); + + zoomMaille(layer); } function styleMailleLastObs() { @@ -550,17 +564,21 @@ function styleMailleLastObs() { function filterMaille(feature, layer) { id = feature.properties.id_type; - if (id === 25 ) { - cities.addLayer(layer); - } - else if ( id === 26) { + if ( id === 26) { department.addLayer(layer); + // Need to do it there otherwise it will be + // in front of cities featureGroup + department.bringToBack(); + } + else if (id === 25 ) { + cities.addLayer(layer); } else if ( id === 27) { tenCell.addLayer(layer); } else { oneCell.addLayer(layer); + oneCell.bringToFront(); } } diff --git a/static/mapHome.js b/static/mapHome.js index e627cd5af..4d1b37bc8 100644 --- a/static/mapHome.js +++ b/static/mapHome.js @@ -65,11 +65,18 @@ $(function(){ // Zoom on the popup on observation click - currentLayer.on('click', function(e){ - if (map.getZoom()<14) { - map.setView(e.latlng, 14); - } - }); + // currentLayer.on('click', function(e){ + // console.log(e.layer.feature); + // bounds = e.layer.feature.geometry.coordinates; + // bounds = bounds.map(b => { + // return b.map(c => { + // return c.map(d => [d[1], d[0]]) + // }) + // }) + // map.fitBounds(bounds, {"duration": 1}); + // //map.setView(e.latlng, 8); + + // }); }); diff --git a/templates/ficheCommune.html b/templates/ficheCommune.html index 47fd007f2..3532bdf30 100644 --- a/templates/ficheCommune.html +++ b/templates/ficheCommune.html @@ -16,8 +16,10 @@ + + From 16f83aae691c2745630c5ed782f5940a1ee29340 Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Tue, 13 Jul 2021 10:32:01 +0200 Subject: [PATCH 03/30] feat(map): Updated sql to match db modifications Changed the sql files to be able to reproduce the changes done on the database. --- data/atlas.sql | 6 +-- data/gn2/atlas_ref_geo.sql | 8 ++-- data/gn2/atlas_synthese.sql | 87 +++++++++++++++++++---------------- data/observations_mailles.sql | 9 +++- 4 files changed, 62 insertions(+), 48 deletions(-) diff --git a/data/atlas.sql b/data/atlas.sql index b634e51fc..9ee6a5abb 100644 --- a/data/atlas.sql +++ b/data/atlas.sql @@ -23,14 +23,14 @@ CREATE MATERIALIZED VIEW atlas.vm_observations AS s.dateobs, s.observateurs, s.altitude_retenue, - s.the_geom_point::geometry('POINT',3857), + s.the_geom_point, s.effectif_total, tx.cd_ref, st_asgeojson(ST_Transform(ST_SetSrid(s.the_geom_point, 3857), 4326)) as geojson_point, - diffusion_level + s.diffusion_level FROM synthese.syntheseff s LEFT JOIN atlas.vm_taxref tx ON tx.cd_nom = s.cd_nom - JOIN atlas.t_layer_territoire m ON ST_Intersects(m.the_geom, s.the_geom_point); + JOIN atlas.t_layer_territoire m ON ST_Intersects(s.the_geom_point, m.the_geom); CREATE UNIQUE INDEX ON atlas.vm_observations (id_observation); CREATE INDEX ON atlas.vm_observations (cd_ref); diff --git a/data/gn2/atlas_ref_geo.sql b/data/gn2/atlas_ref_geo.sql index a997915f2..f00436a0c 100644 --- a/data/gn2/atlas_ref_geo.sql +++ b/data/gn2/atlas_ref_geo.sql @@ -49,11 +49,11 @@ END$$; CREATE MATERIALIZED VIEW atlas.t_mailles_territoire AS SELECT st_transform(c.geom, 3857)::geometry('MultiPolygon',3857) as the_geom, st_asgeojson(st_transform(c.geom, 4326)) AS geojson_maille, - id_area as id_maille + id_area as id_maille, + id_type FROM ref_geo.l_areas c -JOIN ref_geo.bib_areas_types t -ON t.id_type = c.id_type -WHERE t.type_code = :type_maille; + -- See if st_intersects is the right function (st_within ?) + JOIN atlas.t_layer_territoire tlt ON st_intersects(tlt.the_geom, st_transform(c.geom, 3857)) CREATE UNIQUE INDEX t_mailles_territoire_id_maille_idx ON atlas.t_mailles_territoire diff --git a/data/gn2/atlas_synthese.sql b/data/gn2/atlas_synthese.sql index 0b0face08..ff6590342 100644 --- a/data/gn2/atlas_synthese.sql +++ b/data/gn2/atlas_synthese.sql @@ -1,40 +1,49 @@ -- Creation d'une vue permettant de reproduire le contenu de la table du même nom dans les versions précédentes - -CREATE VIEW synthese.syntheseff AS -WITH areas AS ( - SELECT DISTINCT ON (sa.id_synthese, t.type_code) - sa.id_synthese, - sa.id_area, - a.centroid, - st_transform(centroid, 3857) as centroid_3857, - t.type_code - FROM synthese.cor_area_synthese sa - JOIN ref_geo.l_areas a ON sa.id_area = a.id_area - JOIN ref_geo.bib_areas_types t ON a.id_type = t.id_type - WHERE type_code IN ('M10', 'COM', 'DEP') - ), obs_data AS ( - SELECT s.id_synthese, - s.cd_nom, - s.date_min AS dateobs, - s.observers AS observateurs, - (s.altitude_min + s.altitude_max) / 2 AS altitude_retenue, - CASE - WHEN dl.cd_nomenclature = '1' THEN - (SELECT centroid_3857 FROM areas a WHERE a.id_synthese = s.id_synthese AND type_code = 'COM' LIMIT 1) - WHEN dl.cd_nomenclature = '2' THEN - (SELECT centroid_3857 FROM areas a WHERE a.id_synthese = s.id_synthese AND type_code = 'M10' LIMIT 1) - WHEN dl.cd_nomenclature = '3' THEN - (SELECT centroid_3857 FROM areas a WHERE a.id_synthese = s.id_synthese AND type_code = 'DEP' LIMIT 1) - ELSE st_transform(s.the_geom_point, 3857) - END AS the_geom_point, - s.count_min AS effectif_total, - dl.cd_nomenclature::int as diffusion_level - FROM synthese.synthese s - LEFT OUTER JOIN synthese.t_nomenclatures dl ON s.id_nomenclature_diffusion_level = dl.id_nomenclature - LEFT OUTER JOIN synthese.t_nomenclatures st ON s.id_nomenclature_observation_status = st.id_nomenclature - WHERE (NOT dl.cd_nomenclature = '4'::text OR id_nomenclature_diffusion_level IS NULL) -- Filtre données non diffusable code "4" ou pas de diffusion spécifiée - AND st.cd_nomenclature = 'Pr'-- seulement les données présentes (status_observation = ) -) +CREATE OR REPLACE VIEW synthese.syntheseff +AS WITH areas AS ( + SELECT DISTINCT ON (sa.id_synthese, t.type_code) sa.id_synthese, + sa.id_area, + a.centroid, + st_transform(a.geom, 3857) AS geom, + st_transform(a.centroid, 3857) AS centroid_3857, + t.type_code + FROM synthese.cor_area_synthese sa + JOIN ref_geo.l_areas a ON sa.id_area = a.id_area + JOIN ref_geo.bib_areas_types t ON a.id_type = t.id_type + WHERE t.type_code::text = ANY (ARRAY['M10'::character varying::text, 'M1'::character varying::text, 'COM'::character varying::text, 'DEP'::character varying::text]) + ), obs_data AS ( + SELECT s.id_synthese, + s.cd_nom, + s.date_min AS dateobs, + s.observers AS observateurs, + (s.altitude_min + s.altitude_max) / 2 AS altitude_retenue, + CASE + WHEN dl.cd_nomenclature::text = '1'::text THEN ( SELECT a.geom + FROM areas a + WHERE a.id_synthese = s.id_synthese AND a.type_code::text = 'COM'::text + LIMIT 1) + WHEN dl.cd_nomenclature::text = '2'::text THEN ( SELECT a.geom + FROM areas a + WHERE a.id_synthese = s.id_synthese AND a.type_code::text = 'M10'::text + LIMIT 1) + WHEN dl.cd_nomenclature::text = '3'::text THEN ( SELECT a.geom + FROM areas a + WHERE a.id_synthese = s.id_synthese AND a.type_code::text = 'DEP'::text + LIMIT 1) + ELSE ( SELECT a.geom + FROM areas a + WHERE a.id_synthese = s.id_synthese AND a.type_code::text = 'M1'::text + LIMIT 1) + -- ELSE st_transform(s.the_geom_point, 3857) + END AS the_geom_point, + s.count_min AS effectif_total, + dl.cd_nomenclature::integer AS diffusion_level + FROM synthese.synthese s + LEFT JOIN synthese.t_nomenclatures dl ON s.id_nomenclature_diffusion_level = dl.id_nomenclature + LEFT JOIN synthese.t_nomenclatures st ON s.id_nomenclature_observation_status = st.id_nomenclature + WHERE (NOT dl.cd_nomenclature::text = '4'::text OR s.id_nomenclature_diffusion_level IS NULL) + AND st.cd_nomenclature::text = 'Pr'::text + ) SELECT d.id_synthese, d.cd_nom, d.dateobs, @@ -43,6 +52,6 @@ WITH areas AS ( d.the_geom_point, d.effectif_total, c.insee, - diffusion_level -FROM obs_data d -JOIN atlas.l_communes c ON st_intersects(d.the_geom_point, c.the_geom); + d.diffusion_level + FROM obs_data d + LEFT JOIN atlas.l_communes c ON st_within(d.the_geom_point, c.the_geom); diff --git a/data/observations_mailles.sql b/data/observations_mailles.sql index 41b4a0fbc..306a02400 100644 --- a/data/observations_mailles.sql +++ b/data/observations_mailles.sql @@ -4,13 +4,18 @@ CREATE MATERIALIZED VIEW atlas.vm_observations_mailles AS SELECT obs.cd_ref, obs.id_observation, m.id_maille, + m.id_type, m.geojson_maille, date_part('year', dateobs) as annee FROM atlas.vm_observations obs - JOIN atlas.t_mailles_territoire m ON st_intersects(obs.the_geom_point, m.the_geom) + JOIN atlas.t_mailles_territoire m ON st_equals(obs.the_geom_point, m.the_geom) WITH DATA; create unique index on atlas.vm_observations_mailles (id_observation); create index on atlas.vm_observations_mailles (id_maille); create index on atlas.vm_observations_mailles (cd_ref); -create index on atlas.vm_observations_mailles (geojson_maille); +-- create index on atlas.vm_observations_mailles (geojson_maille); +-- This line produces this error : +-- SQL Error [54000]: ERROR: index row requires 8400 bytes, maximum size is 8191 +-- ERROR: index row requires 8400 bytes, maximum size is 8191 +-- ERROR: index row requires 8400 bytes, maximum size is 8191 From 15e5bff9a7fe1305821814615dcd0f174bea83ac Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Thu, 15 Jul 2021 17:05:48 +0200 Subject: [PATCH 04/30] style(map): add hover and click effect Tested if it was possible to add hover and click effect to hightlight a specific cell. Added ZIndex values for the filter to be more robust --- static/mapGenerator.js | 29 ++++++++++++++++++++++++++++ static/mapHome.js | 44 +++++++++++++++++++++++------------------- 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/static/mapGenerator.js b/static/mapGenerator.js index 7de0da5fd..23bf8513e 100644 --- a/static/mapGenerator.js +++ b/static/mapGenerator.js @@ -195,6 +195,7 @@ function styleMaille(feature) { function zoomMaille(layer) { layer.on('click', function(e){ + layer.setStyle(styleMailleClickedOrHover()); bounds = e.sourceTarget.feature.geometry.coordinates; bounds = bounds.map(b => { return b.map(c => { @@ -551,6 +552,13 @@ function onEachFeatureMailleLastObs(feature, layer) { filterMaille(feature, layer); zoomMaille(layer); + + layer.on('mouseover', function () { + this.setStyle(styleMailleClickedOrHover()); + }); + layer.on('mouseout', function () { + this.setStyle(styleMailleLastObs()); + }); } function styleMailleLastObs() { @@ -562,21 +570,42 @@ function styleMailleLastObs() { }; } +function styleMailleClickedOrHover() { + return { + ...styleMailleLastObs, + fillColor:styleMailleLastObs.color, + fillOpacity: 0.4 + } +} + +function resetStyleMailles() { + // set style for all cells + map.eachLayer(function(layer){ + if (layer.feature && layer.feature.properties.id_type) { + layer.setStyle(styleMailleLastObs()) + }; + }); +} + function filterMaille(feature, layer) { id = feature.properties.id_type; if ( id === 26) { + layer.setZIndex(1); department.addLayer(layer); // Need to do it there otherwise it will be // in front of cities featureGroup department.bringToBack(); } else if (id === 25 ) { + layer.setZIndex(2); cities.addLayer(layer); } else if ( id === 27) { + layer.setZIndex(3); tenCell.addLayer(layer); } else { + layer.setZIndex(4); oneCell.addLayer(layer); oneCell.bringToFront(); } diff --git a/static/mapHome.js b/static/mapHome.js index 4d1b37bc8..0ea83ef33 100644 --- a/static/mapHome.js +++ b/static/mapHome.js @@ -18,26 +18,30 @@ $(function(){ displayMailleLayerLastObs(observations) // interaction list - map - $('.tabEspece').click(function(){ - $(this).siblings().removeClass('current'); - $(this).addClass('current'); - var id_observation = $(this).attr('idSynthese'); - p = (currentLayer._layers); - var selectLayer; - for (var key in p) { - if (find_id_observation_in_array(p[key].feature.properties.list_id_observation, id_observation) ){ - selectLayer = p[key]; - } + + $('.tabEspece').click(function(){ + $(this).siblings().removeClass('current'); + $(this).addClass('current'); + var id_observation = $(this).attr('idSynthese'); + p = (currentLayer._layers); + var selectLayer; + for (var key in p) { + if (find_id_observation_in_array(p[key].feature.properties.list_id_observation, id_observation) ){ + selectLayer = p[key]; } + } - selectLayer.openPopup(); - var bounds = L.latLngBounds([]); - var layerBounds = selectLayer.getBounds(); - bounds.extend(layerBounds); - map.fitBounds(bounds, { - maxZoom : 12 - }); + resetStyleMailles() + + selectLayer.setStyle(styleMailleClickedOrHover()); + selectLayer.openPopup(); + var bounds = L.latLngBounds([]); + var layerBounds = selectLayer.getBounds(); + bounds.extend(layerBounds); + map.fitBounds(bounds, { + maxZoom : 12 }); + }); } // Display point layer @@ -57,8 +61,8 @@ $(function(){ selectLayer = p[key]; } } - selectLayer.openPopup(); - map.setView(selectLayer._latlng, 14); + selectLayer.openPopup(selectLayer._latlng); + map.setView(selectLayer._latlng, 14); }) } @@ -90,4 +94,4 @@ htmlLegendPoint = " { return b.map(c => { @@ -553,9 +552,10 @@ function onEachFeatureMailleLastObs(feature, layer) { zoomMaille(layer); - layer.on('mouseover', function () { - this.setStyle(styleMailleClickedOrHover()); + layer.on('mouseover', function (layer) { + this.setStyle(styleMailleClickedOrHover(layer.target)); }); + layer.on('mouseout', function () { this.setStyle(styleMailleLastObs()); }); @@ -565,16 +565,33 @@ function styleMailleLastObs() { return { opacity: 1, weight: 2, - color: "red", + color: "#333333", fillOpacity: 0 }; } -function styleMailleClickedOrHover() { +function styleMailleClickedOrHover(layer) { + var id = layer.feature.properties.id_type; + var fillColor = getComputedStyle(document.body).getPropertyValue('--main-color'); + var fillOpacity = 0.5; + + if ( id === 26) { + var fillOpacity = 0.2; + } + else if (id === 25 ) { + var fillOpacity = 0.4; + } + else if ( id === 27) { + var fillOpacity = 0.6; + } + else { + var fillOpacity = 0.85; + } + var options = layer.options; return { - ...styleMailleLastObs, - fillColor:styleMailleLastObs.color, - fillOpacity: 0.4 + ...options, + fillColor: fillColor, + fillOpacity: fillOpacity } } @@ -590,22 +607,18 @@ function resetStyleMailles() { function filterMaille(feature, layer) { id = feature.properties.id_type; if ( id === 26) { - layer.setZIndex(1); department.addLayer(layer); // Need to do it there otherwise it will be // in front of cities featureGroup department.bringToBack(); } else if (id === 25 ) { - layer.setZIndex(2); cities.addLayer(layer); } else if ( id === 27) { - layer.setZIndex(3); tenCell.addLayer(layer); } else { - layer.setZIndex(4); oneCell.addLayer(layer); oneCell.bringToFront(); } diff --git a/static/mapHome.js b/static/mapHome.js index 0ea83ef33..45bb04e32 100644 --- a/static/mapHome.js +++ b/static/mapHome.js @@ -32,9 +32,8 @@ $(function(){ } resetStyleMailles() - - selectLayer.setStyle(styleMailleClickedOrHover()); - selectLayer.openPopup(); + selectLayer.setStyle(styleMailleClickedOrHover(selectLayer)); + selectLayer.openPopup(selectLayer._bounds.getCenter()); var bounds = L.latLngBounds([]); var layerBounds = selectLayer.getBounds(); bounds.extend(layerBounds); From eb2c73e65d6cff2e4506bc1ac9c7a7be2a541368 Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Tue, 20 Jul 2021 15:27:33 +0200 Subject: [PATCH 07/30] style: Add main-color-rgb to liste espece Added a new var (main-color-rgb) which is the same as main-color variable but with a rgb description enabling to set different opacity depending on the state (hover or click) of the row in the tabEspece --- static/css/index.css | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/static/css/index.css b/static/css/index.css index 30ef61229..680a89a3a 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -291,14 +291,19 @@ table#tableEspece { } .tabEspece:hover { - background-color: #cccccc; + background-color: rgba(var(--main-color-rgb), 0.2); } + .tabEspece.current { - background-color: #e6e6e6; + background-color: rgba(var(--main-color-rgb), 0.4); font-weight: bold; } +.tabEspece.before { + opacity: 0.5; +} + .singleTaxon { cursor: pointer; } From 246639e07da994c35297f0ef29807d634d064bba Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Tue, 20 Jul 2021 15:30:05 +0200 Subject: [PATCH 08/30] feat: better layering and click management Now the layers stack properly when the user activate or deactivate them from the Control (top hand right corner). Add a selected variable enabling the clicked cell to stay filled even if the mouse exits the cell (mouseout event called) --- static/mapGenerator.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/static/mapGenerator.js b/static/mapGenerator.js index 4f0da5d19..65d57ef6d 100644 --- a/static/mapGenerator.js +++ b/static/mapGenerator.js @@ -37,9 +37,14 @@ function generateMap() { L.control.layers(null, overlays).addTo(map) // Activate layers - for (var key in overlays) { - map.addLayer(overlays[key]) - } + Object.values(overlays).forEach(e => map.addLayer(e)) + + // Keep Layers in the same order as specified by the + // overlays variable so Departement under Commune + // under 10km2 under 1km2 + map.on('overlayadd', function(e){ + Object.values(overlays).forEach(e => e.bringToFront()) + }) // Style of territory on map territoryStyle = { @@ -552,12 +557,22 @@ function onEachFeatureMailleLastObs(feature, layer) { zoomMaille(layer); + var selected = false; + + layer.on('click', function (layer) { + resetStyleMailles(); + this.setStyle(styleMailleClickedOrHover(layer.target)); + selected = true; + }); layer.on('mouseover', function (layer) { this.setStyle(styleMailleClickedOrHover(layer.target)); + selected = false; }); layer.on('mouseout', function () { - this.setStyle(styleMailleLastObs()); + if (!selected) { + this.setStyle(styleMailleLastObs()); + } }); } From c05b02f269a8d6f9fdb04246b7a16c108ffcdff5 Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Tue, 20 Jul 2021 15:35:02 +0200 Subject: [PATCH 09/30] style: removed unsused css directive added before Before was used to find a way to set background opacity other than by using rgb formulation --- static/css/index.css | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/static/css/index.css b/static/css/index.css index 680a89a3a..746b6c2a3 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -291,7 +291,7 @@ table#tableEspece { } .tabEspece:hover { - background-color: rgba(var(--main-color-rgb), 0.2); + background-color: --main-color; } @@ -300,10 +300,6 @@ table#tableEspece { font-weight: bold; } -.tabEspece.before { - opacity: 0.5; -} - .singleTaxon { cursor: pointer; } From 59146e59fabd87d1aa7599e16ed224b518e579e6 Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Tue, 20 Jul 2021 15:49:58 +0200 Subject: [PATCH 10/30] style: forgot to put back rgba for tabEspece.hover --- static/css/index.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/static/css/index.css b/static/css/index.css index 746b6c2a3..d0be72a77 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -291,10 +291,9 @@ table#tableEspece { } .tabEspece:hover { - background-color: --main-color; + background-color: rgba(var(--main-color-rgb), 0.2); } - .tabEspece.current { background-color: rgba(var(--main-color-rgb), 0.4); font-weight: bold; From f847c92317d08a93ddda7767f5110c4eaf83830f Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Wed, 21 Jul 2021 12:21:49 +0200 Subject: [PATCH 11/30] fix: corrected sql query and corrected id_type Sql query was from vm_observations whereas we are in a Maille function so it should be from vm_observations_maille Corrected a copy paste mistake on id_type Added dateobs in lastObservationsCommune --- .../repositories/vmObservationsMaillesRepository.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/atlas/modeles/repositories/vmObservationsMaillesRepository.py b/atlas/modeles/repositories/vmObservationsMaillesRepository.py index 81a7e8523..0a0533313 100644 --- a/atlas/modeles/repositories/vmObservationsMaillesRepository.py +++ b/atlas/modeles/repositories/vmObservationsMaillesRepository.py @@ -114,6 +114,7 @@ def lastObservationsCommuneMaille(connection, mylimit, insee): taxon = o.lb_nom temp = { "cd_ref": o.cd_ref, + "dateobs": o.dateobs, "taxon": taxon, "geojson_maille": json.loads(o.geojson_maille), "id_maille": o.id_maille, @@ -126,14 +127,12 @@ def lastObservationsCommuneMaille(connection, mylimit, insee): # Use for API def getObservationsTaxonCommuneMaille(connection, insee, cd_ref): sql = """ - SELECT - o.cd_ref, t.id_maille, t.id_type, t.geojson_maille, + SELECT + o.cd_ref, o.id_maille, o.id_type, o.geojson_maille, o.the_geom, extract(YEAR FROM o.dateobs) as annee - FROM atlas.vm_observations o + FROM atlas.vm_observations_mailles o JOIN atlas.vm_communes c - ON ST_INTERSECTS(o.the_geom_point, c.the_geom) - JOIN atlas.t_mailles_territoire t - ON ST_EQUALS(t.the_geom, o.the_geom_point) + ON ST_INTERSECTS(o.the_geom, c.the_geom) WHERE o.cd_ref = :thiscdref AND c.insee = :thisInsee ORDER BY id_maille """ @@ -142,7 +141,7 @@ def getObservationsTaxonCommuneMaille(connection, insee, cd_ref): for o in observations: temp = { "id_maille": o.id_maille, - "id_type": o.id_maille, + "id_type": o.id_type, "nb_observations": 1, "annee": o.annee, "geojson_maille": json.loads(o.geojson_maille), From 0482100fe5958c106600f4f3857066b05d79cbd0 Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Wed, 21 Jul 2021 12:27:45 +0200 Subject: [PATCH 12/30] refactor: add function to gather better city obs The older function took oservations based on Insee whereas it should take info from the observations requested before. This new function is based on the observations dictionnary that is computed from the vm_observations (maille or not) and take the taxons info Used this function in atlasRoutes.py --- atlas/atlasRoutes.py | 5 +- .../repositories/vmTaxonsRepository.py | 71 ++++++++++++++++++- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/atlas/atlasRoutes.py b/atlas/atlasRoutes.py index 5a96a238e..34ba2a4d3 100644 --- a/atlas/atlasRoutes.py +++ b/atlas/atlasRoutes.py @@ -191,7 +191,7 @@ def ficheCommune(insee): session = utils.loadSession() connection = utils.engine.connect() - listTaxons = vmTaxonsRepository.getTaxonsCommunes(connection, insee) + commune = vmCommunesRepository.getCommuneFromInsee(connection, insee) if current_app.config["AFFICHAGE_MAILLE"]: observations = vmObservationsMaillesRepository.lastObservationsCommuneMaille( @@ -201,6 +201,9 @@ def ficheCommune(insee): observations = vmObservationsRepository.lastObservationsCommune( connection, current_app.config["NB_LAST_OBS"], insee ) + + # listTaxons = vmTaxonsRepository.getTaxonsCommunes(connection, insee) + listTaxons = vmTaxonsRepository.get_taxons_from_obs(connection, observations) observers = vmObservationsRepository.getObserversCommunes(connection, insee) diff --git a/atlas/modeles/repositories/vmTaxonsRepository.py b/atlas/modeles/repositories/vmTaxonsRepository.py index ac32e16e0..b2d4e323e 100644 --- a/atlas/modeles/repositories/vmTaxonsRepository.py +++ b/atlas/modeles/repositories/vmTaxonsRepository.py @@ -1,6 +1,6 @@ # -*- coding:utf-8 -*- - +from collections import Counter from flask import current_app import unicodedata @@ -9,6 +9,75 @@ from .. import utils +def get_taxons_from_obs(connection, observations: dict): + """ + Gets infos on taxons present in the observations + + Args: + observations (dict): observation dictionnary + + Returns: + dict: dictionnary of all info on taxons. Contains: + - taxons + - 'nom_complet_html' + - 'nb_obs' + - 'nom_vern' + - 'cd_ref' + - 'last_obs' + - 'group2_inpn' + - 'patrimonial' + - 'protection_stricte' + - 'path' + - 'id_media' + - nbObsTotal + - nb_obs_total + """ + # Get the nb of observations for each cd_ref to have the nb_obs + count = Counter(obs['cd_ref'] for obs in observations) + cd_refs = tuple(count.keys()) + + # Get the most recent observation date for each cd_ref. + # TODO: there must be a better way than 2 loops... + max_obs = [max(obs['dateobs'] for obs in observations if obs['cd_ref'] == cd_ref) for cd_ref in cd_refs] + + # Request + sql = """ + SELECT DISTINCT + t.cd_ref, t.nom_complet_html, t.nom_vern, + t.group2_inpn, t.patrimonial, t.protection_stricte, + m.url, m.chemin, m.id_media + from atlas.vm_taxons t + LEFT JOIN atlas.vm_medias m ON m.cd_ref=t.cd_ref AND m.id_type={} + WHERE t.cd_ref in {} + GROUP BY t.cd_ref, t.nom_vern, t.nom_complet_html, t.group2_inpn, + t.patrimonial, t.protection_stricte, m.url, m.chemin, m.id_media + ORDER BY t.cd_ref + """.format(current_app.config['ATTR_MAIN_PHOTO'], cd_refs) + + req = connection.execute(text(sql)) + + taxon_commune_list = [] + + nb_obs_total = 0 + + for r in req: + nb_obs = count[r.cd_ref] + temp = { + 'nom_complet_html': r.nom_complet_html, + 'nb_obs': nb_obs, + 'nom_vern': r.nom_vern, + 'cd_ref': r.cd_ref, + 'last_obs': max_obs.year, + 'group2_inpn': utils.deleteAccent(r.group2_inpn), + 'patrimonial': r.patrimonial, + 'protection_stricte': r.protection_stricte, + 'path': utils.findPath(r), + 'id_media': r.id_media + } + taxon_commune_list.append(temp) + nb_obs_total = nb_obs_total + nb_obs + return {'taxons': taxon_commune_list, 'nbObsTotal': nb_obs_total} + # With distinct the result in a array not an object, 0: lb_nom, 1: nom_vern def getTaxonsCommunes(connection, insee): From c0a66e8791b6a4ddd9a55256ec41779226528d5e Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Wed, 21 Jul 2021 12:33:05 +0200 Subject: [PATCH 13/30] feat: worked on communes to make the sensi work mapHome.js: Clarified code mapGenerator.js: Move control layer bloc for more clarity as well Added id_type in property for geojson of commune Corrected a bug by added addTo(map) in displayMailleLayerLastObs mapHome.js: simplified code --- static/mapCommune.js | 2 ++ static/mapGenerator.js | 58 +++++++++++++++++++++++++++++------------- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/static/mapCommune.js b/static/mapCommune.js index 1a39fcb89..c9d799c15 100644 --- a/static/mapCommune.js +++ b/static/mapCommune.js @@ -111,6 +111,8 @@ function displayObsTaxonMaille(insee, cd_ref) { }).done(function(observations) { $("#loadingGif").hide(); map.removeLayer(currentLayer); + // Remove the layer filter because it messes up + control.remove(); displayMailleLayerCommune(observations); }); } diff --git a/static/mapGenerator.js b/static/mapGenerator.js index 65d57ef6d..81681bfb2 100644 --- a/static/mapGenerator.js +++ b/static/mapGenerator.js @@ -10,18 +10,6 @@ function generateMap() { baseMap = {}; baseMap[configuration.MAP.FIRST_MAP.tileName] = firstMapTile; - department = L.featureGroup(); - cities = L.featureGroup(); - tenCell = L.featureGroup(); - oneCell = L.featureGroup(); - - var overlays = { - "Departement": department, - "Communes": cities, - "10km²": tenCell, - "1km²": oneCell - } - var map = L.map("map", { crs: L.CRS.EPSG3857, center: configuration.MAP.LAT_LONG, @@ -33,15 +21,29 @@ function generateMap() { fullscreenControl: true }); - // Add layers - L.control.layers(null, overlays).addTo(map) + department = L.featureGroup(); + cities = L.featureGroup(); + tenCell = L.featureGroup(); + oneCell = L.featureGroup(); + var overlays = { + "Departement": department, + "Communes": cities, + "10km²": tenCell, + "1km²": oneCell + } + // Add layers + control = L.control.layers(null, overlays) + + if (configuration.AFFICHAGE_MAILLE) { + control.addTo(map) + } // Activate layers Object.values(overlays).forEach(e => map.addLayer(e)) // Keep Layers in the same order as specified by the // overlays variable so Departement under Commune - // under 10km2 under 1km2 + // under 10km2 under 1km2 map.on('overlayadd', function(e){ Object.values(overlays).forEach(e => e.bringToFront()) }) @@ -300,7 +302,9 @@ function generateGeojsonMailleCommune(observations) { while (i < observations.length) { geometry = observations[i].geojson_maille; idMaille = observations[i].id_maille; + idType = observations[i].id_type; properties = { + id_type: idType, id_maille: idMaille, nb_observations: 1, last_observation: observations[i].annee @@ -429,6 +433,25 @@ function onEachFeaturePointLastObs(feature, layer) { feature.properties.cd_ref + "'> Fiche espèce " ); + + id = feature.properties.id_type; + if ( id === 26) { + department.addLayer(layer); + // Need to do it there otherwise it will be + // in front of cities featureGroup + department.bringToBack(); + } + else if (id === 25 ) { + cities.addLayer(layer); + } + else if ( id === 27) { + tenCell.addLayer(layer); + } + else { + oneCell.addLayer(layer); + oneCell.bringToFront(); + } + } function onEachFeaturePointCommune(feature, layer) { @@ -486,7 +509,6 @@ function displayMarkerLayerPointLastObs(observationsPoint) { ); } }); - map.addLayer(currentLayer); if (typeof divLegendeFicheCommuneHome !== "undefined") { legend.onAdd = function(map) { @@ -686,7 +708,9 @@ function displayMailleLayerLastObs(observations) { onEachFeature: onEachFeatureMailleLastObs, style: styleMailleLastObs }); - // currentLayer.addTo(map); + // Very important otherwise currentLayer cannot be removed by + // mapCommune.js + currentLayer.addTo(map); } // Legend From f9aa473157d52d3ef1e5f46c0a960290f1338e00 Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Wed, 21 Jul 2021 12:34:48 +0200 Subject: [PATCH 14/30] fix: removed unused import --- atlas/modeles/repositories/vmTaxonsRepository.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/atlas/modeles/repositories/vmTaxonsRepository.py b/atlas/modeles/repositories/vmTaxonsRepository.py index b2d4e323e..60d1afc7b 100644 --- a/atlas/modeles/repositories/vmTaxonsRepository.py +++ b/atlas/modeles/repositories/vmTaxonsRepository.py @@ -3,8 +3,6 @@ from collections import Counter from flask import current_app -import unicodedata - from sqlalchemy.sql import text from .. import utils From 1e43d7797fec68e851d647949727750a96342c4b Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Mon, 26 Jul 2021 11:33:04 +0200 Subject: [PATCH 15/30] chore: remove useless case --- static/mapGenerator.js | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/static/mapGenerator.js b/static/mapGenerator.js index 81681bfb2..16fe8d7ce 100644 --- a/static/mapGenerator.js +++ b/static/mapGenerator.js @@ -433,25 +433,6 @@ function onEachFeaturePointLastObs(feature, layer) { feature.properties.cd_ref + "'> Fiche espèce " ); - - id = feature.properties.id_type; - if ( id === 26) { - department.addLayer(layer); - // Need to do it there otherwise it will be - // in front of cities featureGroup - department.bringToBack(); - } - else if (id === 25 ) { - cities.addLayer(layer); - } - else if ( id === 27) { - tenCell.addLayer(layer); - } - else { - oneCell.addLayer(layer); - oneCell.bringToFront(); - } - } function onEachFeaturePointCommune(feature, layer) { From dc551bec9018aa97e53c9f001f48cf8e6fac3256 Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Mon, 26 Jul 2021 11:35:01 +0200 Subject: [PATCH 16/30] fix(sql): returns the date object, not a str To be compatible with the new get_taxons_from_obs function, the date object must be returned and not an str conversion --- atlas/modeles/repositories/vmObservationsRepository.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atlas/modeles/repositories/vmObservationsRepository.py b/atlas/modeles/repositories/vmObservationsRepository.py index 3c7d14615..afb564c2d 100644 --- a/atlas/modeles/repositories/vmObservationsRepository.py +++ b/atlas/modeles/repositories/vmObservationsRepository.py @@ -115,7 +115,7 @@ def lastObservationsCommune(connection, mylimit, insee): temp = dict(o) temp.pop("the_geom_point", None) temp["geojson_point"] = json.loads(o.geojson_point or "{}") - temp["dateobs"] = str(o.dateobs) + temp["dateobs"] = o.dateobs obsList.append(temp) return obsList From f85ef5bd1b38356f801a65de1bb2737bc56a3d65 Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Mon, 26 Jul 2021 11:37:56 +0200 Subject: [PATCH 17/30] fix(sql): corrected bug when no species are found The function crashed when there were no taxons in a particular city. Changed also the request for it to be more versatile in terms of cd_ref (bugged when there was one taxon) --- .../repositories/vmTaxonsRepository.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/atlas/modeles/repositories/vmTaxonsRepository.py b/atlas/modeles/repositories/vmTaxonsRepository.py index 60d1afc7b..d92768701 100644 --- a/atlas/modeles/repositories/vmTaxonsRepository.py +++ b/atlas/modeles/repositories/vmTaxonsRepository.py @@ -30,13 +30,21 @@ def get_taxons_from_obs(connection, observations: dict): - nbObsTotal - nb_obs_total """ + # Init + taxon_commune_list = [] + nb_obs_total = 0 + # Get the nb of observations for each cd_ref to have the nb_obs count = Counter(obs['cd_ref'] for obs in observations) cd_refs = tuple(count.keys()) + if not cd_refs: + return {'taxons': taxon_commune_list, + 'nbObsTotal': nb_obs_total} + # Get the most recent observation date for each cd_ref. # TODO: there must be a better way than 2 loops... - max_obs = [max(obs['dateobs'] for obs in observations if obs['cd_ref'] == cd_ref) for cd_ref in cd_refs] + max_obs = {cd_ref: max(obs['dateobs'] for obs in observations if obs['cd_ref'] == cd_ref) for cd_ref in cd_refs} # Request sql = """ @@ -46,18 +54,14 @@ def get_taxons_from_obs(connection, observations: dict): m.url, m.chemin, m.id_media from atlas.vm_taxons t LEFT JOIN atlas.vm_medias m ON m.cd_ref=t.cd_ref AND m.id_type={} - WHERE t.cd_ref in {} + WHERE t.cd_ref in ({}) GROUP BY t.cd_ref, t.nom_vern, t.nom_complet_html, t.group2_inpn, t.patrimonial, t.protection_stricte, m.url, m.chemin, m.id_media ORDER BY t.cd_ref - """.format(current_app.config['ATTR_MAIN_PHOTO'], cd_refs) + """.format(current_app.config['ATTR_MAIN_PHOTO'], ','.join(str(k) for k in cd_refs)) req = connection.execute(text(sql)) - taxon_commune_list = [] - - nb_obs_total = 0 - for r in req: nb_obs = count[r.cd_ref] temp = { @@ -65,7 +69,7 @@ def get_taxons_from_obs(connection, observations: dict): 'nb_obs': nb_obs, 'nom_vern': r.nom_vern, 'cd_ref': r.cd_ref, - 'last_obs': max_obs.year, + 'last_obs': max_obs[r.cd_ref].year, 'group2_inpn': utils.deleteAccent(r.group2_inpn), 'patrimonial': r.patrimonial, 'protection_stricte': r.protection_stricte, From ec88d60d6764e5929239750f4183b2ff7f2e3da5 Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Mon, 26 Jul 2021 12:18:16 +0200 Subject: [PATCH 18/30] feat(sql): Changed vm to be compatible with sensib atlas.sql: add centroid to vm_observations to be compatible with point mode Changed st_contains by st_intersect as mentionned in an issue observations_mailles.sql: Changed tables where to take infos Make this vm independant from vm_observations Removed index since it take to much space atlas_synthese.sql: Changed index since it was not working with observations that do not have type_code Returns Point when there is no sensibility instead of a mesh cell --- data/atlas.sql | 6 +++--- data/gn2/atlas_synthese.sql | 11 +++-------- data/observations_mailles.sql | 21 ++++++++++++++++----- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/data/atlas.sql b/data/atlas.sql index 9ee6a5abb..669251b90 100644 --- a/data/atlas.sql +++ b/data/atlas.sql @@ -23,10 +23,10 @@ CREATE MATERIALIZED VIEW atlas.vm_observations AS s.dateobs, s.observateurs, s.altitude_retenue, - s.the_geom_point, + st_centroid(s.the_geom_point) as the_geom_point, s.effectif_total, tx.cd_ref, - st_asgeojson(ST_Transform(ST_SetSrid(s.the_geom_point, 3857), 4326)) as geojson_point, + st_asgeojson(ST_Transform(ST_SetSrid(st_centroid(s.the_geom_point), 3857), 4326)) as geojson_point, s.diffusion_level FROM synthese.syntheseff s LEFT JOIN atlas.vm_taxref tx ON tx.cd_nom = s.cd_nom @@ -269,7 +269,7 @@ c.commune_maj, c.the_geom, st_asgeojson(st_transform(c.the_geom, 4326)) as commune_geojson FROM atlas.l_communes c -JOIN atlas.t_layer_territoire t ON ST_CONTAINS(ST_BUFFER(t.the_geom,200), c.the_geom); +JOIN atlas.t_layer_territoire t ON ST_INTERSECTS(t.the_geom, c.the_geom); CREATE UNIQUE INDEX ON atlas.vm_communes (insee); CREATE INDEX index_gist_vm_communes_the_geom ON atlas.vm_communes USING gist (the_geom); diff --git a/data/gn2/atlas_synthese.sql b/data/gn2/atlas_synthese.sql index ff6590342..b8e45c8ef 100644 --- a/data/gn2/atlas_synthese.sql +++ b/data/gn2/atlas_synthese.sql @@ -1,7 +1,7 @@ -- Creation d'une vue permettant de reproduire le contenu de la table du même nom dans les versions précédentes CREATE OR REPLACE VIEW synthese.syntheseff AS WITH areas AS ( - SELECT DISTINCT ON (sa.id_synthese, t.type_code) sa.id_synthese, + SELECT DISTINCT ON (sa.id_synthese, a.id_area) sa.id_synthese, sa.id_area, a.centroid, st_transform(a.geom, 3857) AS geom, @@ -30,19 +30,14 @@ AS WITH areas AS ( FROM areas a WHERE a.id_synthese = s.id_synthese AND a.type_code::text = 'DEP'::text LIMIT 1) - ELSE ( SELECT a.geom - FROM areas a - WHERE a.id_synthese = s.id_synthese AND a.type_code::text = 'M1'::text - LIMIT 1) - -- ELSE st_transform(s.the_geom_point, 3857) + ELSE st_transform(s.the_geom_point, 3857) END AS the_geom_point, s.count_min AS effectif_total, dl.cd_nomenclature::integer AS diffusion_level FROM synthese.synthese s LEFT JOIN synthese.t_nomenclatures dl ON s.id_nomenclature_diffusion_level = dl.id_nomenclature LEFT JOIN synthese.t_nomenclatures st ON s.id_nomenclature_observation_status = st.id_nomenclature - WHERE (NOT dl.cd_nomenclature::text = '4'::text OR s.id_nomenclature_diffusion_level IS NULL) - AND st.cd_nomenclature::text = 'Pr'::text + WHERE (NOT dl.cd_nomenclature::text = '4'::text OR s.id_nomenclature_diffusion_level IS NULL) AND st.cd_nomenclature::text = 'Pr'::text ) SELECT d.id_synthese, d.cd_nom, diff --git a/data/observations_mailles.sql b/data/observations_mailles.sql index 306a02400..d21777f35 100644 --- a/data/observations_mailles.sql +++ b/data/observations_mailles.sql @@ -1,14 +1,25 @@ -- Creation de la VM des observations de chaque taxon par mailles... CREATE MATERIALIZED VIEW atlas.vm_observations_mailles AS - SELECT obs.cd_ref, - obs.id_observation, + SELECT tx.cd_ref, + s.id_synthese AS id_observation, + s.dateobs, m.id_maille, m.id_type, + m.the_geom, m.geojson_maille, - date_part('year', dateobs) as annee - FROM atlas.vm_observations obs - JOIN atlas.t_mailles_territoire m ON st_equals(obs.the_geom_point, m.the_geom) + date_part('year'::text, s.dateobs) AS annee + FROM synthese.syntheseff s + LEFT JOIN atlas.vm_taxref tx ON tx.cd_nom = s.cd_nom + JOIN atlas.t_mailles_territoire m ON + CASE + -- since s.the_geom_point can be either a point or a geometry + -- corresponding to m.the_geom, we need to use a case + -- Intersects the geom point with a 1km mesh cell if no sensibility + WHEN s.diffusion_level = 5 THEN st_intersects(s.the_geom_point, m.the_geom) AND m.id_type = 29 + -- We have no id from the syntheseff view so need to st_equals... + ELSE st_equals(s.the_geom_point, m.the_geom) + END WITH DATA; create unique index on atlas.vm_observations_mailles (id_observation); From 5e91d4f59ea8934592f7fd5c732d6a980769dafb Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Mon, 26 Jul 2021 13:51:28 +0200 Subject: [PATCH 19/30] style(species): Add css variable for species tab --- static/css/listEspeces.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/static/css/listEspeces.css b/static/css/listEspeces.css index 82cc34921..77be5067d 100644 --- a/static/css/listEspeces.css +++ b/static/css/listEspeces.css @@ -111,12 +111,13 @@ ul#statHierarchy { } tbody tr:hover { + background-color: rgba(var(--main-color-rgb), 0.2) !important; /*background-color: #cccccc !important;*/ cursor: pointer; } tbody tr.current { - background-color: #e6e6e6 !important; + background-color: rgba(var(--main-color-rgb), 0.4) !important; font-weight: bold; } From c486cdcccf3918f259bb2fec4cb864e2130afc09 Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Mon, 26 Jul 2021 15:21:04 +0200 Subject: [PATCH 20/30] fix(map): bump leaflet version and rm snogilop Need to update leaflet for bringToFront/BringToBack to work Remove dependency to snogilop (had to test for compatibility) --- static/lib/leaflet/leaflet.js | 14 +++++--------- templates/ficheCommune.html | 1 - 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/static/lib/leaflet/leaflet.js b/static/lib/leaflet/leaflet.js index 383896a6b..bc9ef0f55 100644 --- a/static/lib/leaflet/leaflet.js +++ b/static/lib/leaflet/leaflet.js @@ -1,9 +1,5 @@ -/* - Leaflet 1.0.2, a JS library for interactive maps. http://leafletjs.com - (c) 2010-2016 Vladimir Agafonkin, (c) 2010-2011 CloudMade -*/ -!function(t,e,i){function n(){var e=t.L;o.noConflict=function(){return t.L=e,this},t.L=o}var o={version:"1.0.2"};"object"==typeof module&&"object"==typeof module.exports?module.exports=o:"function"==typeof define&&define.amd&&define(o),"undefined"!=typeof t&&n(),o.Util={extend:function(t){var e,i,n,o;for(i=1,n=arguments.length;i1}}(),o.Point=function(t,e,i){this.x=i?Math.round(t):t,this.y=i?Math.round(e):e},o.Point.prototype={clone:function(){return new o.Point(this.x,this.y)},add:function(t){return this.clone()._add(o.point(t))},_add:function(t){return this.x+=t.x,this.y+=t.y,this},subtract:function(t){return this.clone()._subtract(o.point(t))},_subtract:function(t){return this.x-=t.x,this.y-=t.y,this},divideBy:function(t){return this.clone()._divideBy(t)},_divideBy:function(t){return this.x/=t,this.y/=t,this},multiplyBy:function(t){return this.clone()._multiplyBy(t)},_multiplyBy:function(t){return this.x*=t,this.y*=t,this},scaleBy:function(t){return new o.Point(this.x*t.x,this.y*t.y)},unscaleBy:function(t){return new o.Point(this.x/t.x,this.y/t.y)},round:function(){return this.clone()._round()},_round:function(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this},floor:function(){return this.clone()._floor()},_floor:function(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this},ceil:function(){return this.clone()._ceil()},_ceil:function(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this},distanceTo:function(t){t=o.point(t);var e=t.x-this.x,i=t.y-this.y;return Math.sqrt(e*e+i*i)},equals:function(t){return t=o.point(t),t.x===this.x&&t.y===this.y},contains:function(t){return t=o.point(t),Math.abs(t.x)<=Math.abs(this.x)&&Math.abs(t.y)<=Math.abs(this.y)},toString:function(){return"Point("+o.Util.formatNum(this.x)+", "+o.Util.formatNum(this.y)+")"}},o.point=function(t,e,n){return t instanceof o.Point?t:o.Util.isArray(t)?new o.Point(t[0],t[1]):t===i||null===t?t:"object"==typeof t&&"x"in t&&"y"in t?new o.Point(t.x,t.y):new o.Point(t,e,n)},o.Bounds=function(t,e){if(t)for(var i=e?[t,e]:t,n=0,o=i.length;n=this.min.x&&i.x<=this.max.x&&e.y>=this.min.y&&i.y<=this.max.y},intersects:function(t){t=o.bounds(t);var e=this.min,i=this.max,n=t.min,s=t.max,r=s.x>=e.x&&n.x<=i.x,a=s.y>=e.y&&n.y<=i.y;return r&&a},overlaps:function(t){t=o.bounds(t);var e=this.min,i=this.max,n=t.min,s=t.max,r=s.x>e.x&&n.xe.y&&n.y0&&new RegExp("(^|\\s)"+e+"(\\s|$)").test(n)},addClass:function(t,e){if(t.classList!==i)for(var n=o.Util.splitWords(e),s=0,r=n.length;s=n.lat&&i.lat<=s.lat&&e.lng>=n.lng&&i.lng<=s.lng},intersects:function(t){t=o.latLngBounds(t);var e=this._southWest,i=this._northEast,n=t.getSouthWest(),s=t.getNorthEast(),r=s.lat>=e.lat&&n.lat<=i.lat,a=s.lng>=e.lng&&n.lng<=i.lng;return r&&a},overlaps:function(t){t=o.latLngBounds(t);var e=this._southWest,i=this._northEast,n=t.getSouthWest(),s=t.getNorthEast(),r=s.lat>e.lat&&n.late.lng&&n.lngthis.options.maxZoom?this.setZoom(t):this},panInsideBounds:function(t,e){this._enforcingBounds=!0;var i=this.getCenter(),n=this._limitCenter(i,this._zoom,o.latLngBounds(t));return i.equals(n)||this.panTo(n,e),this._enforcingBounds=!1,this},invalidateSize:function(t){if(!this._loaded)return this;t=o.extend({animate:!1,pan:!0},t===!0?{animate:!0}:t);var e=this.getSize();this._sizeChanged=!0,this._lastCenter=null;var i=this.getSize(),n=e.divideBy(2).round(),s=i.divideBy(2).round(),r=n.subtract(s);return r.x||r.y?(t.animate&&t.pan?this.panBy(r):(t.pan&&this._rawPanBy(r),this.fire("move"),t.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(o.bind(this.fire,this,"moveend"),200)):this.fire("moveend")),this.fire("resize",{oldSize:e,newSize:i})):this},stop:function(){return this.setZoom(this._limitZoom(this._zoom)),this.options.zoomSnap||this.fire("viewreset"),this._stop()},locate:function(t){if(t=this._locateOptions=o.extend({timeout:1e4,watch:!1},t),!("geolocation"in navigator))return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var e=o.bind(this._handleGeolocationResponse,this),i=o.bind(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(e,i,t):navigator.geolocation.getCurrentPosition(e,i,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var e=t.code,i=t.message||(1===e?"permission denied":2===e?"position unavailable":"timeout");this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:e,message:"Geolocation error: "+i+"."})},_handleGeolocationResponse:function(t){var e=t.coords.latitude,i=t.coords.longitude,n=new o.LatLng(e,i),s=n.toBounds(t.coords.accuracy),r=this._locateOptions;if(r.setView){var a=this.getBoundsZoom(s);this.setView(n,r.maxZoom?Math.min(a,r.maxZoom):a)}var h={latlng:n,bounds:s,timestamp:t.timestamp};for(var l in t.coords)"number"==typeof t.coords[l]&&(h[l]=t.coords[l]);this.fire("locationfound",h)},addHandler:function(t,e){if(!e)return this;var i=this[t]=new e(this);return this._handlers.push(i),this.options[t]&&i.enable(),this},remove:function(){if(this._initEvents(!0),this._containerId!==this._container._leaflet_id)throw new Error("Map container is being reused by another instance");try{delete this._container._leaflet_id,delete this._containerId}catch(t){this._container._leaflet_id=i,this._containerId=i}o.DomUtil.remove(this._mapPane),this._clearControlPos&&this._clearControlPos(),this._clearHandlers(),this._loaded&&this.fire("unload");for(var t in this._layers)this._layers[t].remove();return this},createPane:function(t,e){var i="leaflet-pane"+(t?" leaflet-"+t.replace("Pane","")+"-pane":""),n=o.DomUtil.create("div",i,e||this._mapPane);return t&&(this._panes[t]=n),n},getCenter:function(){return this._checkIfLoaded(),this._lastCenter&&!this._moved()?this._lastCenter:this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds(),e=this.unproject(t.getBottomLeft()),i=this.unproject(t.getTopRight());return new o.LatLngBounds(e,i)},getMinZoom:function(){return this.options.minZoom===i?this._layersMinZoom||0:this.options.minZoom},getMaxZoom:function(){return this.options.maxZoom===i?this._layersMaxZoom===i?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(t,e,i){t=o.latLngBounds(t),i=o.point(i||[0,0]);var n=this.getZoom()||0,s=this.getMinZoom(),r=this.getMaxZoom(),a=t.getNorthWest(),h=t.getSouthEast(),l=this.getSize().subtract(i),u=this.project(h,n).subtract(this.project(a,n)),c=o.Browser.any3d?this.options.zoomSnap:1,d=Math.min(l.x/u.x,l.y/u.y);return n=this.getScaleZoom(d,n),c&&(n=Math.round(n/(c/100))*(c/100),n=e?Math.ceil(n/c)*c:Math.floor(n/c)*c),Math.max(s,Math.min(r,n))},getSize:function(){return this._size&&!this._sizeChanged||(this._size=new o.Point(this._container.clientWidth,this._container.clientHeight),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(t,e){var i=this._getTopLeftPoint(t,e);return new o.Bounds(i,i.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._pixelOrigin},getPixelWorldBounds:function(t){return this.options.crs.getProjectedBounds(t===i?this.getZoom():t)},getPane:function(t){return"string"==typeof t?this._panes[t]:t},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t,e){var n=this.options.crs;return e=e===i?this._zoom:e,n.scale(t)/n.scale(e)},getScaleZoom:function(t,e){var n=this.options.crs;e=e===i?this._zoom:e;var o=n.zoom(t*n.scale(e));return isNaN(o)?1/0:o},project:function(t,e){return e=e===i?this._zoom:e,this.options.crs.latLngToPoint(o.latLng(t),e)},unproject:function(t,e){return e=e===i?this._zoom:e,this.options.crs.pointToLatLng(o.point(t),e)},layerPointToLatLng:function(t){var e=o.point(t).add(this.getPixelOrigin());return this.unproject(e)},latLngToLayerPoint:function(t){var e=this.project(o.latLng(t))._round();return e._subtract(this.getPixelOrigin())},wrapLatLng:function(t){return this.options.crs.wrapLatLng(o.latLng(t))},distance:function(t,e){return this.options.crs.distance(o.latLng(t),o.latLng(e))},containerPointToLayerPoint:function(t){return o.point(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return o.point(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){var e=this.containerPointToLayerPoint(o.point(t));return this.layerPointToLatLng(e)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(o.latLng(t)))},mouseEventToContainerPoint:function(t){return o.DomEvent.getMousePosition(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){var e=this._container=o.DomUtil.get(t);if(!e)throw new Error("Map container not found.");if(e._leaflet_id)throw new Error("Map container is already initialized.");o.DomEvent.addListener(e,"scroll",this._onScroll,this),this._containerId=o.Util.stamp(e)},_initLayout:function(){var t=this._container;this._fadeAnimated=this.options.fadeAnimation&&o.Browser.any3d,o.DomUtil.addClass(t,"leaflet-container"+(o.Browser.touch?" leaflet-touch":"")+(o.Browser.retina?" leaflet-retina":"")+(o.Browser.ielt9?" leaflet-oldie":"")+(o.Browser.safari?" leaflet-safari":"")+(this._fadeAnimated?" leaflet-fade-anim":""));var e=o.DomUtil.getStyle(t,"position");"absolute"!==e&&"relative"!==e&&"fixed"!==e&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos(); -},_initPanes:function(){var t=this._panes={};this._paneRenderers={},this._mapPane=this.createPane("mapPane",this._container),o.DomUtil.setPosition(this._mapPane,new o.Point(0,0)),this.createPane("tilePane"),this.createPane("shadowPane"),this.createPane("overlayPane"),this.createPane("markerPane"),this.createPane("tooltipPane"),this.createPane("popupPane"),this.options.markerZoomAnimation||(o.DomUtil.addClass(t.markerPane,"leaflet-zoom-hide"),o.DomUtil.addClass(t.shadowPane,"leaflet-zoom-hide"))},_resetView:function(t,e){o.DomUtil.setPosition(this._mapPane,new o.Point(0,0));var i=!this._loaded;this._loaded=!0,e=this._limitZoom(e),this.fire("viewprereset");var n=this._zoom!==e;this._moveStart(n)._move(t,e)._moveEnd(n),this.fire("viewreset"),i&&this.fire("load")},_moveStart:function(t){return t&&this.fire("zoomstart"),this.fire("movestart")},_move:function(t,e,n){e===i&&(e=this._zoom);var o=this._zoom!==e;return this._zoom=e,this._lastCenter=t,this._pixelOrigin=this._getNewPixelOrigin(t),(o||n&&n.pinch)&&this.fire("zoom",n),this.fire("move",n)},_moveEnd:function(t){return t&&this.fire("zoomend"),this.fire("moveend")},_stop:function(){return o.Util.cancelAnimFrame(this._flyToFrame),this._panAnim&&this._panAnim.stop(),this},_rawPanBy:function(t){o.DomUtil.setPosition(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_panInsideMaxBounds:function(){this._enforcingBounds||this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(e){if(o.DomEvent){this._targets={},this._targets[o.stamp(this._container)]=this;var i=e?"off":"on";o.DomEvent[i](this._container,"click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress",this._handleDOMEvent,this),this.options.trackResize&&o.DomEvent[i](t,"resize",this._onResize,this),o.Browser.any3d&&this.options.transform3DLimit&&this[i]("moveend",this._onMoveEnd)}},_onResize:function(){o.Util.cancelAnimFrame(this._resizeRequest),this._resizeRequest=o.Util.requestAnimFrame(function(){this.invalidateSize({debounceMoveend:!0})},this)},_onScroll:function(){this._container.scrollTop=0,this._container.scrollLeft=0},_onMoveEnd:function(){var t=this._getMapPanePos();Math.max(Math.abs(t.x),Math.abs(t.y))>=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,e){for(var i,n=[],s="mouseout"===e||"mouseover"===e,r=t.target||t.srcElement,a=!1;r;){if(i=this._targets[o.stamp(r)],i&&("click"===e||"preclick"===e)&&!t._simulated&&this._draggableMoved(i)){a=!0;break}if(i&&i.listens(e,!0)){if(s&&!o.DomEvent._isExternalTarget(r,t))break;if(n.push(i),s)break}if(r===this._container)break;r=r.parentNode}return n.length||a||s||!o.DomEvent._isExternalTarget(r,t)||(n=[this]),n},_handleDOMEvent:function(t){if(this._loaded&&!o.DomEvent._skipped(t)){var e="keypress"===t.type&&13===t.keyCode?"click":t.type;"mousedown"===e&&o.DomUtil.preventOutline(t.target||t.srcElement),this._fireDOMEvent(t,e)}},_fireDOMEvent:function(t,e,i){if("click"===t.type){var n=o.Util.extend({},t);n.type="preclick",this._fireDOMEvent(n,n.type,i)}if(!t._stopped&&(i=(i||[]).concat(this._findEventTargets(t,e)),i.length)){var s=i[0];"contextmenu"===e&&s.listens(e,!0)&&o.DomEvent.preventDefault(t);var r={originalEvent:t};if("keypress"!==t.type){var a=s instanceof o.Marker;r.containerPoint=a?this.latLngToContainerPoint(s.getLatLng()):this.mouseEventToContainerPoint(t),r.layerPoint=this.containerPointToLayerPoint(r.containerPoint),r.latlng=a?s.getLatLng():this.layerPointToLatLng(r.layerPoint)}for(var h=0;h0?Math.round(t-e)/2:Math.max(0,Math.ceil(t))-Math.max(0,Math.floor(e))},_limitZoom:function(t){var e=this.getMinZoom(),i=this.getMaxZoom(),n=o.Browser.any3d?this.options.zoomSnap:1;return n&&(t=Math.round(t/n)*n),Math.max(e,Math.min(i,t))},_onPanTransitionStep:function(){this.fire("move")},_onPanTransitionEnd:function(){o.DomUtil.removeClass(this._mapPane,"leaflet-pan-anim"),this.fire("moveend")},_tryAnimatedPan:function(t,e){var i=this._getCenterOffset(t)._floor();return!((e&&e.animate)!==!0&&!this.getSize().contains(i))&&(this.panBy(i,e),!0)},_createAnimProxy:function(){var t=this._proxy=o.DomUtil.create("div","leaflet-proxy leaflet-zoom-animated");this._panes.mapPane.appendChild(t),this.on("zoomanim",function(e){var i=o.DomUtil.TRANSFORM,n=t.style[i];o.DomUtil.setTransform(t,this.project(e.center,e.zoom),this.getZoomScale(e.zoom,1)),n===t.style[i]&&this._animatingZoom&&this._onZoomTransitionEnd()},this),this.on("load moveend",function(){var e=this.getCenter(),i=this.getZoom();o.DomUtil.setTransform(t,this.project(e,i),this.getZoomScale(i,1))},this)},_catchTransitionEnd:function(t){this._animatingZoom&&t.propertyName.indexOf("transform")>=0&&this._onZoomTransitionEnd()},_nothingToAnimate:function(){return!this._container.getElementsByClassName("leaflet-zoom-animated").length},_tryAnimatedZoom:function(t,e,i){if(this._animatingZoom)return!0;if(i=i||{},!this._zoomAnimated||i.animate===!1||this._nothingToAnimate()||Math.abs(e-this._zoom)>this.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(e),s=this._getCenterOffset(t)._divideBy(1-1/n);return!(i.animate!==!0&&!this.getSize().contains(s))&&(o.Util.requestAnimFrame(function(){this._moveStart(!0)._animateZoom(t,e,!0)},this),!0)},_animateZoom:function(t,e,i,n){i&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=e,o.DomUtil.addClass(this._mapPane,"leaflet-zoom-anim")),this.fire("zoomanim",{center:t,zoom:e,noUpdate:n}),setTimeout(o.bind(this._onZoomTransitionEnd,this),250)},_onZoomTransitionEnd:function(){this._animatingZoom&&(o.DomUtil.removeClass(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom),o.Util.requestAnimFrame(function(){this._moveEnd(!0)},this))}}),o.map=function(t,e){return new o.Map(t,e)},o.Layer=o.Evented.extend({options:{pane:"overlayPane",nonBubblingEvents:[],attribution:null},addTo:function(t){return t.addLayer(this),this},remove:function(){return this.removeFrom(this._map||this._mapToAdd)},removeFrom:function(t){return t&&t.removeLayer(this),this},getPane:function(t){return this._map.getPane(t?this.options[t]||t:this.options.pane)},addInteractiveTarget:function(t){return this._map._targets[o.stamp(t)]=this,this},removeInteractiveTarget:function(t){return delete this._map._targets[o.stamp(t)],this},getAttribution:function(){return this.options.attribution},_layerAdd:function(t){var e=t.target;if(e.hasLayer(this)){if(this._map=e,this._zoomAnimated=e._zoomAnimated,this.getEvents){var i=this.getEvents();e.on(i,this),this.once("remove",function(){e.off(i,this)},this)}this.onAdd(e),this.getAttribution&&this._map.attributionControl&&this._map.attributionControl.addAttribution(this.getAttribution()),this.fire("add"),e.fire("layeradd",{layer:this})}}}),o.Map.include({addLayer:function(t){var e=o.stamp(t);return this._layers[e]?this:(this._layers[e]=t,t._mapToAdd=this,t.beforeAdd&&t.beforeAdd(this),this.whenReady(t._layerAdd,t),this)},removeLayer:function(t){var e=o.stamp(t);return this._layers[e]?(this._loaded&&t.onRemove(this),t.getAttribution&&this.attributionControl&&this.attributionControl.removeAttribution(t.getAttribution()),delete this._layers[e],this._loaded&&(this.fire("layerremove",{layer:t}),t.fire("remove")),t._map=t._mapToAdd=null,this):this},hasLayer:function(t){return!!t&&o.stamp(t)in this._layers},eachLayer:function(t,e){for(var i in this._layers)t.call(e,this._layers[i]);return this},_addLayers:function(t){t=t?o.Util.isArray(t)?t:[t]:[];for(var e=0,i=t.length;ethis._layersMaxZoom&&this.setZoom(this._layersMaxZoom),this.options.minZoom===i&&this._layersMinZoom&&this.getZoom()100&&n<500||t.target._simulatedClick&&!t._simulated?void o.DomEvent.stop(t):(o.DomEvent._lastClick=i,void e(t))}},o.DomEvent.addListener=o.DomEvent.on,o.DomEvent.removeListener=o.DomEvent.off,o.PosAnimation=o.Evented.extend({run:function(t,e,i,n){this.stop(),this._el=t,this._inProgress=!0,this._duration=i||.25,this._easeOutPower=1/Math.max(n||.5,.2),this._startPos=o.DomUtil.getPosition(t),this._offset=e.subtract(this._startPos),this._startTime=+new Date,this.fire("start"),this._animate()},stop:function(){this._inProgress&&(this._step(!0),this._complete())},_animate:function(){this._animId=o.Util.requestAnimFrame(this._animate,this),this._step()},_step:function(t){var e=+new Date-this._startTime,i=1e3*this._duration;e1e-7;l++)e=r*Math.sin(h),e=Math.pow((1-e)/(1+e),r/2),u=Math.PI/2-2*Math.atan(a*e)-h,h+=u;return new o.LatLng(h*i,t.x*i/n)}},o.CRS.EPSG3395=o.extend({},o.CRS.Earth,{code:"EPSG:3395",projection:o.Projection.Mercator,transformation:function(){var t=.5/(Math.PI*o.Projection.Mercator.R);return new o.Transformation(t,.5,-t,.5)}()}),o.GridLayer=o.Layer.extend({options:{tileSize:256,opacity:1,updateWhenIdle:o.Browser.mobile,updateWhenZooming:!0,updateInterval:200,zIndex:1,bounds:null,minZoom:0,maxZoom:i,noWrap:!1,pane:"tilePane",className:"",keepBuffer:2},initialize:function(t){o.setOptions(this,t)},onAdd:function(){this._initContainer(),this._levels={},this._tiles={},this._resetView(),this._update()},beforeAdd:function(t){t._addZoomLimit(this)},onRemove:function(t){this._removeAllTiles(),o.DomUtil.remove(this._container),t._removeZoomLimit(this),this._container=null,this._tileZoom=null},bringToFront:function(){return this._map&&(o.DomUtil.toFront(this._container),this._setAutoZIndex(Math.max)),this},bringToBack:function(){return this._map&&(o.DomUtil.toBack(this._container),this._setAutoZIndex(Math.min)),this},getContainer:function(){return this._container},setOpacity:function(t){return this.options.opacity=t,this._updateOpacity(),this},setZIndex:function(t){return this.options.zIndex=t,this._updateZIndex(),this},isLoading:function(){return this._loading},redraw:function(){return this._map&&(this._removeAllTiles(),this._update()),this},getEvents:function(){var t={viewprereset:this._invalidateAll,viewreset:this._resetView,zoom:this._resetView,moveend:this._onMoveEnd};return this.options.updateWhenIdle||(this._onMove||(this._onMove=o.Util.throttle(this._onMoveEnd,this.options.updateInterval,this)),t.move=this._onMove),this._zoomAnimated&&(t.zoomanim=this._animateZoom),t},createTile:function(){return e.createElement("div")},getTileSize:function(){var t=this.options.tileSize;return t instanceof o.Point?t:new o.Point(t,t)},_updateZIndex:function(){this._container&&this.options.zIndex!==i&&null!==this.options.zIndex&&(this._container.style.zIndex=this.options.zIndex)},_setAutoZIndex:function(t){for(var e,i=this.getPane().children,n=-t(-(1/0),1/0),o=0,s=i.length;othis.options.maxZoom||in&&this._retainParent(s,r,a,n))},_retainChildren:function(t,e,i,n){for(var s=2*t;s<2*t+2;s++)for(var r=2*e;r<2*e+2;r++){var a=new o.Point(s,r);a.z=i+1;var h=this._tileCoordsToKey(a),l=this._tiles[h];l&&l.active?l.retain=!0:(l&&l.loaded&&(l.retain=!0),i+1this.options.maxZoom||this.options.minZoom!==i&&s1)return void this._setView(t,s);for(var m=a.min.y;m<=a.max.y;m++)for(var p=a.min.x;p<=a.max.x;p++){var f=new o.Point(p,m);if(f.z=this._tileZoom,this._isValidTile(f)){var g=this._tiles[this._tileCoordsToKey(f)];g?g.current=!0:l.push(f)}}if(l.sort(function(t,e){return t.distanceTo(h)-e.distanceTo(h)}),0!==l.length){this._loading||(this._loading=!0,this.fire("loading"));var v=e.createDocumentFragment();for(p=0;pi.max.x)||!e.wrapLat&&(t.yi.max.y))return!1}if(!this.options.bounds)return!0;var n=this._tileCoordsToBounds(t);return o.latLngBounds(this.options.bounds).overlaps(n)},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToBounds:function(t){var e=this._map,i=this.getTileSize(),n=t.scaleBy(i),s=n.add(i),r=e.unproject(n,t.z),a=e.unproject(s,t.z);return this.options.noWrap||(r=e.wrapLatLng(r),a=e.wrapLatLng(a)),new o.LatLngBounds(r,a)},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var e=t.split(":"),i=new o.Point(+e[0],+e[1]);return i.z=+e[2],i},_removeTile:function(t){var e=this._tiles[t];e&&(o.DomUtil.remove(e.el),delete this._tiles[t],this.fire("tileunload",{tile:e.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){o.DomUtil.addClass(t,"leaflet-tile");var e=this.getTileSize();t.style.width=e.x+"px",t.style.height=e.y+"px",t.onselectstart=o.Util.falseFn,t.onmousemove=o.Util.falseFn,o.Browser.ielt9&&this.options.opacity<1&&o.DomUtil.setOpacity(t,this.options.opacity),o.Browser.android&&!o.Browser.android23&&(t.style.WebkitBackfaceVisibility="hidden")},_addTile:function(t,e){var i=this._getTilePos(t),n=this._tileCoordsToKey(t),s=this.createTile(this._wrapCoords(t),o.bind(this._tileReady,this,t));this._initTile(s),this.createTile.length<2&&o.Util.requestAnimFrame(o.bind(this._tileReady,this,t,null,s)),o.DomUtil.setPosition(s,i),this._tiles[n]={el:s,coords:t,current:!0},e.appendChild(s),this.fire("tileloadstart",{tile:s,coords:t})},_tileReady:function(t,e,i){if(this._map){e&&this.fire("tileerror",{error:e,tile:i,coords:t});var n=this._tileCoordsToKey(t);i=this._tiles[n],i&&(i.loaded=+new Date,this._map._fadeAnimated?(o.DomUtil.setOpacity(i.el,0),o.Util.cancelAnimFrame(this._fadeFrame),this._fadeFrame=o.Util.requestAnimFrame(this._updateOpacity,this)):(i.active=!0,this._pruneTiles()),e||(o.DomUtil.addClass(i.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:i.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),o.Browser.ielt9||!this._map._fadeAnimated?o.Util.requestAnimFrame(this._pruneTiles,this):setTimeout(o.bind(this._pruneTiles,this),250)))}},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var e=new o.Point(this._wrapX?o.Util.wrapNum(t.x,this._wrapX):t.x,this._wrapY?o.Util.wrapNum(t.y,this._wrapY):t.y);return e.z=t.z,e},_pxBoundsToTileRange:function(t){var e=this.getTileSize();return new o.Bounds(t.min.unscaleBy(e).floor(),t.max.unscaleBy(e).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}}),o.gridLayer=function(t){return new o.GridLayer(t)},o.TileLayer=o.GridLayer.extend({options:{minZoom:0,maxZoom:18,maxNativeZoom:null,minNativeZoom:null,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1},initialize:function(t,e){this._url=t,e=o.setOptions(this,e),e.detectRetina&&o.Browser.retina&&e.maxZoom>0&&(e.tileSize=Math.floor(e.tileSize/2),e.zoomReverse?(e.zoomOffset--,e.minZoom++):(e.zoomOffset++,e.maxZoom--),e.minZoom=Math.max(0,e.minZoom)),"string"==typeof e.subdomains&&(e.subdomains=e.subdomains.split("")),o.Browser.android||this.on("tileunload",this._onTileRemove)},setUrl:function(t,e){return this._url=t,e||this.redraw(),this},createTile:function(t,i){var n=e.createElement("img");return o.DomEvent.on(n,"load",o.bind(this._tileOnLoad,this,i,n)),o.DomEvent.on(n,"error",o.bind(this._tileOnError,this,i,n)),this.options.crossOrigin&&(n.crossOrigin=""),n.alt="",n.setAttribute("role","presentation"),n.src=this.getTileUrl(t),n},getTileUrl:function(t){var e={r:o.Browser.retina?"@2x":"",s:this._getSubdomain(t),x:t.x,y:t.y,z:this._getZoomForUrl()};if(this._map&&!this._map.options.crs.infinite){var i=this._globalTileRange.max.y-t.y;this.options.tms&&(e.y=i),e["-y"]=i}return o.Util.template(this._url,o.extend(e,this.options))},_tileOnLoad:function(t,e){o.Browser.ielt9?setTimeout(o.bind(t,this,null,e),0):t(null,e)},_tileOnError:function(t,e,i){var n=this.options.errorTileUrl;n&&(e.src=n),t(i,e)},getTileSize:function(){var t=this._map,e=o.GridLayer.prototype.getTileSize.call(this),i=this._tileZoom+this.options.zoomOffset,n=this.options.minNativeZoom,s=this.options.maxNativeZoom;return null!==n&&is?e.divideBy(t.getZoomScale(s,i)).round():e},_onTileRemove:function(t){t.tile.onload=null},_getZoomForUrl:function(){var t=this._tileZoom,e=this.options.maxZoom,i=this.options.zoomReverse,n=this.options.zoomOffset,o=this.options.minNativeZoom,s=this.options.maxNativeZoom;return i&&(t=e-t),t+=n,null!==o&&ts?s:t},_getSubdomain:function(t){var e=Math.abs(t.x+t.y)%this.options.subdomains.length;return this.options.subdomains[e]},_abortLoading:function(){var t,e;for(t in this._tiles)this._tiles[t].coords.z!==this._tileZoom&&(e=this._tiles[t].el,e.onload=o.Util.falseFn,e.onerror=o.Util.falseFn,e.complete||(e.src=o.Util.emptyImageUrl,o.DomUtil.remove(e)))}}),o.tileLayer=function(t,e){return new o.TileLayer(t,e)},o.TileLayer.WMS=o.TileLayer.extend({defaultWmsParams:{service:"WMS",request:"GetMap",layers:"",styles:"",format:"image/jpeg",transparent:!1,version:"1.1.1"},options:{crs:null,uppercase:!1},initialize:function(t,e){this._url=t;var i=o.extend({},this.defaultWmsParams);for(var n in e)n in this.options||(i[n]=e[n]);e=o.setOptions(this,e),i.width=i.height=e.tileSize*(e.detectRetina&&o.Browser.retina?2:1),this.wmsParams=i},onAdd:function(t){this._crs=this.options.crs||t.options.crs,this._wmsVersion=parseFloat(this.wmsParams.version);var e=this._wmsVersion>=1.3?"crs":"srs";this.wmsParams[e]=this._crs.code,o.TileLayer.prototype.onAdd.call(this,t)},getTileUrl:function(t){var e=this._tileCoordsToBounds(t),i=this._crs.project(e.getNorthWest()),n=this._crs.project(e.getSouthEast()),s=(this._wmsVersion>=1.3&&this._crs===o.CRS.EPSG4326?[n.y,i.x,i.y,n.x]:[i.x,n.y,n.x,i.y]).join(","),r=o.TileLayer.prototype.getTileUrl.call(this,t);return r+o.Util.getParamString(this.wmsParams,r,this.options.uppercase)+(this.options.uppercase?"&BBOX=":"&bbox=")+s},setParams:function(t,e){return o.extend(this.wmsParams,t),e||this.redraw(),this}}),o.tileLayer.wms=function(t,e){return new o.TileLayer.WMS(t,e)},o.ImageOverlay=o.Layer.extend({options:{opacity:1,alt:"",interactive:!1,crossOrigin:!1},initialize:function(t,e,i){this._url=t,this._bounds=o.latLngBounds(e),o.setOptions(this,i)},onAdd:function(){this._image||(this._initImage(),this.options.opacity<1&&this._updateOpacity()),this.options.interactive&&(o.DomUtil.addClass(this._image,"leaflet-interactive"),this.addInteractiveTarget(this._image)),this.getPane().appendChild(this._image),this._reset()},onRemove:function(){o.DomUtil.remove(this._image),this.options.interactive&&this.removeInteractiveTarget(this._image)},setOpacity:function(t){return this.options.opacity=t,this._image&&this._updateOpacity(),this},setStyle:function(t){return t.opacity&&this.setOpacity(t.opacity),this},bringToFront:function(){return this._map&&o.DomUtil.toFront(this._image),this},bringToBack:function(){return this._map&&o.DomUtil.toBack(this._image),this},setUrl:function(t){return this._url=t,this._image&&(this._image.src=t),this},setBounds:function(t){return this._bounds=t,this._map&&this._reset(),this},getEvents:function(){var t={zoom:this._reset,viewreset:this._reset};return this._zoomAnimated&&(t.zoomanim=this._animateZoom),t},getBounds:function(){return this._bounds},getElement:function(){return this._image},_initImage:function(){var t=this._image=o.DomUtil.create("img","leaflet-image-layer "+(this._zoomAnimated?"leaflet-zoom-animated":""));t.onselectstart=o.Util.falseFn,t.onmousemove=o.Util.falseFn,t.onload=o.bind(this.fire,this,"load"),this.options.crossOrigin&&(t.crossOrigin=""),t.src=this._url,t.alt=this.options.alt},_animateZoom:function(t){var e=this._map.getZoomScale(t.zoom),i=this._map._latLngBoundsToNewLayerBounds(this._bounds,t.zoom,t.center).min;o.DomUtil.setTransform(this._image,i,e)},_reset:function(){var t=this._image,e=new o.Bounds(this._map.latLngToLayerPoint(this._bounds.getNorthWest()),this._map.latLngToLayerPoint(this._bounds.getSouthEast())),i=e.getSize();o.DomUtil.setPosition(t,e.min),t.style.width=i.x+"px",t.style.height=i.y+"px"},_updateOpacity:function(){o.DomUtil.setOpacity(this._image,this.options.opacity); -}}),o.imageOverlay=function(t,e,i){return new o.ImageOverlay(t,e,i)},o.Icon=o.Class.extend({initialize:function(t){o.setOptions(this,t)},createIcon:function(t){return this._createIcon("icon",t)},createShadow:function(t){return this._createIcon("shadow",t)},_createIcon:function(t,e){var i=this._getIconUrl(t);if(!i){if("icon"===t)throw new Error("iconUrl not set in Icon options (see the docs).");return null}var n=this._createImg(i,e&&"IMG"===e.tagName?e:null);return this._setIconStyles(n,t),n},_setIconStyles:function(t,e){var i=this.options,n=i[e+"Size"];"number"==typeof n&&(n=[n,n]);var s=o.point(n),r=o.point("shadow"===e&&i.shadowAnchor||i.iconAnchor||s&&s.divideBy(2,!0));t.className="leaflet-marker-"+e+" "+(i.className||""),r&&(t.style.marginLeft=-r.x+"px",t.style.marginTop=-r.y+"px"),s&&(t.style.width=s.x+"px",t.style.height=s.y+"px")},_createImg:function(t,i){return i=i||e.createElement("img"),i.src=t,i},_getIconUrl:function(t){return o.Browser.retina&&this.options[t+"RetinaUrl"]||this.options[t+"Url"]}}),o.icon=function(t){return new o.Icon(t)},o.Icon.Default=o.Icon.extend({options:{iconUrl:"marker-icon.png",iconRetinaUrl:"marker-icon-2x.png",shadowUrl:"marker-shadow.png",iconSize:[25,41],iconAnchor:[12,41],popupAnchor:[1,-34],tooltipAnchor:[16,-28],shadowSize:[41,41]},_getIconUrl:function(t){return o.Icon.Default.imagePath||(o.Icon.Default.imagePath=this._detectIconPath()),(this.options.imagePath||o.Icon.Default.imagePath)+o.Icon.prototype._getIconUrl.call(this,t)},_detectIconPath:function(){var t=o.DomUtil.create("div","leaflet-default-icon-path",e.body),i=o.DomUtil.getStyle(t,"background-image")||o.DomUtil.getStyle(t,"backgroundImage");return e.body.removeChild(t),0===i.indexOf("url")?i.replace(/^url\([\"\']?/,"").replace(/marker-icon\.png[\"\']?\)$/,""):""}}),o.Marker=o.Layer.extend({options:{icon:new o.Icon.Default,interactive:!0,draggable:!1,keyboard:!0,title:"",alt:"",zIndexOffset:0,opacity:1,riseOnHover:!1,riseOffset:250,pane:"markerPane",nonBubblingEvents:["click","dblclick","mouseover","mouseout","contextmenu"]},initialize:function(t,e){o.setOptions(this,e),this._latlng=o.latLng(t)},onAdd:function(t){this._zoomAnimated=this._zoomAnimated&&t.options.markerZoomAnimation,this._zoomAnimated&&t.on("zoomanim",this._animateZoom,this),this._initIcon(),this.update()},onRemove:function(t){this.dragging&&this.dragging.enabled()&&(this.options.draggable=!0,this.dragging.removeHooks()),this._zoomAnimated&&t.off("zoomanim",this._animateZoom,this),this._removeIcon(),this._removeShadow()},getEvents:function(){return{zoom:this.update,viewreset:this.update}},getLatLng:function(){return this._latlng},setLatLng:function(t){var e=this._latlng;return this._latlng=o.latLng(t),this.update(),this.fire("move",{oldLatLng:e,latlng:this._latlng})},setZIndexOffset:function(t){return this.options.zIndexOffset=t,this.update()},setIcon:function(t){return this.options.icon=t,this._map&&(this._initIcon(),this.update()),this._popup&&this.bindPopup(this._popup,this._popup.options),this},getElement:function(){return this._icon},update:function(){if(this._icon){var t=this._map.latLngToLayerPoint(this._latlng).round();this._setPos(t)}return this},_initIcon:function(){var t=this.options,e="leaflet-zoom-"+(this._zoomAnimated?"animated":"hide"),i=t.icon.createIcon(this._icon),n=!1;i!==this._icon&&(this._icon&&this._removeIcon(),n=!0,t.title&&(i.title=t.title),t.alt&&(i.alt=t.alt)),o.DomUtil.addClass(i,e),t.keyboard&&(i.tabIndex="0"),this._icon=i,t.riseOnHover&&this.on({mouseover:this._bringToFront,mouseout:this._resetZIndex});var s=t.icon.createShadow(this._shadow),r=!1;s!==this._shadow&&(this._removeShadow(),r=!0),s&&o.DomUtil.addClass(s,e),this._shadow=s,t.opacity<1&&this._updateOpacity(),n&&this.getPane().appendChild(this._icon),this._initInteraction(),s&&r&&this.getPane("shadowPane").appendChild(this._shadow)},_removeIcon:function(){this.options.riseOnHover&&this.off({mouseover:this._bringToFront,mouseout:this._resetZIndex}),o.DomUtil.remove(this._icon),this.removeInteractiveTarget(this._icon),this._icon=null},_removeShadow:function(){this._shadow&&o.DomUtil.remove(this._shadow),this._shadow=null},_setPos:function(t){o.DomUtil.setPosition(this._icon,t),this._shadow&&o.DomUtil.setPosition(this._shadow,t),this._zIndex=t.y+this.options.zIndexOffset,this._resetZIndex()},_updateZIndex:function(t){this._icon.style.zIndex=this._zIndex+t},_animateZoom:function(t){var e=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center).round();this._setPos(e)},_initInteraction:function(){if(this.options.interactive&&(o.DomUtil.addClass(this._icon,"leaflet-interactive"),this.addInteractiveTarget(this._icon),o.Handler.MarkerDrag)){var t=this.options.draggable;this.dragging&&(t=this.dragging.enabled(),this.dragging.disable()),this.dragging=new o.Handler.MarkerDrag(this),t&&this.dragging.enable()}},setOpacity:function(t){return this.options.opacity=t,this._map&&this._updateOpacity(),this},_updateOpacity:function(){var t=this.options.opacity;o.DomUtil.setOpacity(this._icon,t),this._shadow&&o.DomUtil.setOpacity(this._shadow,t)},_bringToFront:function(){this._updateZIndex(this.options.riseOffset)},_resetZIndex:function(){this._updateZIndex(0)},_getPopupAnchor:function(){return this.options.icon.options.popupAnchor||[0,0]},_getTooltipAnchor:function(){return this.options.icon.options.tooltipAnchor||[0,0]}}),o.marker=function(t,e){return new o.Marker(t,e)},o.DivIcon=o.Icon.extend({options:{iconSize:[12,12],html:!1,bgPos:null,className:"leaflet-div-icon"},createIcon:function(t){var i=t&&"DIV"===t.tagName?t:e.createElement("div"),n=this.options;if(i.innerHTML=n.html!==!1?n.html:"",n.bgPos){var s=o.point(n.bgPos);i.style.backgroundPosition=-s.x+"px "+-s.y+"px"}return this._setIconStyles(i,"icon"),i},createShadow:function(){return null}}),o.divIcon=function(t){return new o.DivIcon(t)},o.DivOverlay=o.Layer.extend({options:{offset:[0,7],className:"",pane:"popupPane"},initialize:function(t,e){o.setOptions(this,t),this._source=e},onAdd:function(t){this._zoomAnimated=t._zoomAnimated,this._container||this._initLayout(),t._fadeAnimated&&o.DomUtil.setOpacity(this._container,0),clearTimeout(this._removeTimeout),this.getPane().appendChild(this._container),this.update(),t._fadeAnimated&&o.DomUtil.setOpacity(this._container,1),this.bringToFront()},onRemove:function(t){t._fadeAnimated?(o.DomUtil.setOpacity(this._container,0),this._removeTimeout=setTimeout(o.bind(o.DomUtil.remove,o.DomUtil,this._container),200)):o.DomUtil.remove(this._container)},getLatLng:function(){return this._latlng},setLatLng:function(t){return this._latlng=o.latLng(t),this._map&&(this._updatePosition(),this._adjustPan()),this},getContent:function(){return this._content},setContent:function(t){return this._content=t,this.update(),this},getElement:function(){return this._container},update:function(){this._map&&(this._container.style.visibility="hidden",this._updateContent(),this._updateLayout(),this._updatePosition(),this._container.style.visibility="",this._adjustPan())},getEvents:function(){var t={zoom:this._updatePosition,viewreset:this._updatePosition};return this._zoomAnimated&&(t.zoomanim=this._animateZoom),t},isOpen:function(){return!!this._map&&this._map.hasLayer(this)},bringToFront:function(){return this._map&&o.DomUtil.toFront(this._container),this},bringToBack:function(){return this._map&&o.DomUtil.toBack(this._container),this},_updateContent:function(){if(this._content){var t=this._contentNode,e="function"==typeof this._content?this._content(this._source||this):this._content;if("string"==typeof e)t.innerHTML=e;else{for(;t.hasChildNodes();)t.removeChild(t.firstChild);t.appendChild(e)}this.fire("contentupdate")}},_updatePosition:function(){if(this._map){var t=this._map.latLngToLayerPoint(this._latlng),e=o.point(this.options.offset),i=this._getAnchor();this._zoomAnimated?o.DomUtil.setPosition(this._container,t.add(i)):e=e.add(t).add(i);var n=this._containerBottom=-e.y,s=this._containerLeft=-Math.round(this._containerWidth/2)+e.x;this._container.style.bottom=n+"px",this._container.style.left=s+"px"}},_getAnchor:function(){return[0,0]}}),o.Popup=o.DivOverlay.extend({options:{maxWidth:300,minWidth:50,maxHeight:null,autoPan:!0,autoPanPaddingTopLeft:null,autoPanPaddingBottomRight:null,autoPanPadding:[5,5],keepInView:!1,closeButton:!0,autoClose:!0,className:""},openOn:function(t){return t.openPopup(this),this},onAdd:function(t){o.DivOverlay.prototype.onAdd.call(this,t),t.fire("popupopen",{popup:this}),this._source&&(this._source.fire("popupopen",{popup:this},!0),this._source instanceof o.Path||this._source.on("preclick",o.DomEvent.stopPropagation))},onRemove:function(t){o.DivOverlay.prototype.onRemove.call(this,t),t.fire("popupclose",{popup:this}),this._source&&(this._source.fire("popupclose",{popup:this},!0),this._source instanceof o.Path||this._source.off("preclick",o.DomEvent.stopPropagation))},getEvents:function(){var t=o.DivOverlay.prototype.getEvents.call(this);return("closeOnClick"in this.options?this.options.closeOnClick:this._map.options.closePopupOnClick)&&(t.preclick=this._close),this.options.keepInView&&(t.moveend=this._adjustPan),t},_close:function(){this._map&&this._map.closePopup(this)},_initLayout:function(){var t="leaflet-popup",e=this._container=o.DomUtil.create("div",t+" "+(this.options.className||"")+" leaflet-zoom-animated");if(this.options.closeButton){var i=this._closeButton=o.DomUtil.create("a",t+"-close-button",e);i.href="#close",i.innerHTML="×",o.DomEvent.on(i,"click",this._onCloseButtonClick,this)}var n=this._wrapper=o.DomUtil.create("div",t+"-content-wrapper",e);this._contentNode=o.DomUtil.create("div",t+"-content",n),o.DomEvent.disableClickPropagation(n).disableScrollPropagation(this._contentNode).on(n,"contextmenu",o.DomEvent.stopPropagation),this._tipContainer=o.DomUtil.create("div",t+"-tip-container",e),this._tip=o.DomUtil.create("div",t+"-tip",this._tipContainer)},_updateLayout:function(){var t=this._contentNode,e=t.style;e.width="",e.whiteSpace="nowrap";var i=t.offsetWidth;i=Math.min(i,this.options.maxWidth),i=Math.max(i,this.options.minWidth),e.width=i+1+"px",e.whiteSpace="",e.height="";var n=t.offsetHeight,s=this.options.maxHeight,r="leaflet-popup-scrolled";s&&n>s?(e.height=s+"px",o.DomUtil.addClass(t,r)):o.DomUtil.removeClass(t,r),this._containerWidth=this._container.offsetWidth},_animateZoom:function(t){var e=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center),i=this._getAnchor();o.DomUtil.setPosition(this._container,e.add(i))},_adjustPan:function(){if(!(!this.options.autoPan||this._map._panAnim&&this._map._panAnim._inProgress)){var t=this._map,e=parseInt(o.DomUtil.getStyle(this._container,"marginBottom"),10)||0,i=this._container.offsetHeight+e,n=this._containerWidth,s=new o.Point(this._containerLeft,-i-this._containerBottom);s._add(o.DomUtil.getPosition(this._container));var r=t.layerPointToContainerPoint(s),a=o.point(this.options.autoPanPadding),h=o.point(this.options.autoPanPaddingTopLeft||a),l=o.point(this.options.autoPanPaddingBottomRight||a),u=t.getSize(),c=0,d=0;r.x+n+l.x>u.x&&(c=r.x+n-u.x+l.x),r.x-c-h.x<0&&(c=r.x-h.x),r.y+i+l.y>u.y&&(d=r.y+i-u.y+l.y),r.y-d-h.y<0&&(d=r.y-h.y),(c||d)&&t.fire("autopanstart").panBy([c,d])}},_onCloseButtonClick:function(t){this._close(),o.DomEvent.stop(t)},_getAnchor:function(){return o.point(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}}),o.popup=function(t,e){return new o.Popup(t,e)},o.Map.mergeOptions({closePopupOnClick:!0}),o.Map.include({openPopup:function(t,e,i){return t instanceof o.Popup||(t=new o.Popup(i).setContent(t)),e&&t.setLatLng(e),this.hasLayer(t)?this:(this._popup&&this._popup.options.autoClose&&this.closePopup(),this._popup=t,this.addLayer(t))},closePopup:function(t){return t&&t!==this._popup||(t=this._popup,this._popup=null),t&&this.removeLayer(t),this}}),o.Layer.include({bindPopup:function(t,e){return t instanceof o.Popup?(o.setOptions(t,e),this._popup=t,t._source=this):(this._popup&&!e||(this._popup=new o.Popup(e,this)),this._popup.setContent(t)),this._popupHandlersAdded||(this.on({click:this._openPopup,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t,e){if(t instanceof o.Layer||(e=t,t=this),t instanceof o.FeatureGroup)for(var i in this._layers){t=this._layers[i];break}return e||(e=t.getCenter?t.getCenter():t.getLatLng()),this._popup&&this._map&&(this._popup._source=t,this._popup.update(),this._map.openPopup(this._popup,e)),this},closePopup:function(){return this._popup&&this._popup._close(),this},togglePopup:function(t){return this._popup&&(this._popup._map?this.closePopup():this.openPopup(t)),this},isPopupOpen:function(){return this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var e=t.layer||t.target;if(this._popup&&this._map)return o.DomEvent.stop(t),e instanceof o.Path?void this.openPopup(t.layer||t.target,t.latlng):void(this._map.hasLayer(this._popup)&&this._popup._source===e?this.closePopup():this.openPopup(e,t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)}}),o.Tooltip=o.DivOverlay.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,interactive:!1,opacity:.9},onAdd:function(t){o.DivOverlay.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&this._source.fire("tooltipopen",{tooltip:this},!0)},onRemove:function(t){o.DivOverlay.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&this._source.fire("tooltipclose",{tooltip:this},!0)},getEvents:function(){var t=o.DivOverlay.prototype.getEvents.call(this);return o.Browser.touch&&!this.options.permanent&&(t.preclick=this._close),t},_close:function(){this._map&&this._map.closeTooltip(this)},_initLayout:function(){var t="leaflet-tooltip",e=t+" "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=o.DomUtil.create("div",e)},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var e=this._map,i=this._container,n=e.latLngToContainerPoint(e.getCenter()),s=e.layerPointToContainerPoint(t),r=this.options.direction,a=i.offsetWidth,h=i.offsetHeight,l=o.point(this.options.offset),u=this._getAnchor();"top"===r?t=t.add(o.point(-a/2+l.x,-h+l.y+u.y,!0)):"bottom"===r?t=t.subtract(o.point(a/2-l.x,-l.y,!0)):"center"===r?t=t.subtract(o.point(a/2+l.x,h/2-u.y+l.y,!0)):"right"===r||"auto"===r&&s.xh&&(s=r,h=a);h>i&&(e[s]=1,this._simplifyDPStep(t,e,i,n,s),this._simplifyDPStep(t,e,i,s,o))},_reducePoints:function(t,e){for(var i=[t[0]],n=1,o=0,s=t.length;ne&&(i.push(t[n]),o=n);return oe.max.x&&(i|=2),t.ye.max.y&&(i|=8),i},_sqDist:function(t,e){var i=e.x-t.x,n=e.y-t.y;return i*i+n*n},_sqClosestPointOnSegment:function(t,e,i,n){var s,r=e.x,a=e.y,h=i.x-r,l=i.y-a,u=h*h+l*l;return u>0&&(s=((t.x-r)*h+(t.y-a)*l)/u,s>1?(r=i.x,a=i.y):s>0&&(r+=h*s,a+=l*s)),h=t.x-r,l=t.y-a,n?h*h+l*l:new o.Point(r,a)}},o.Polyline=o.Path.extend({options:{smoothFactor:1,noClip:!1},initialize:function(t,e){o.setOptions(this,e),this._setLatLngs(t)},getLatLngs:function(){return this._latlngs},setLatLngs:function(t){return this._setLatLngs(t),this.redraw()},isEmpty:function(){return!this._latlngs.length},closestLayerPoint:function(t){for(var e,i,n=1/0,s=null,r=o.LineUtil._sqClosestPointOnSegment,a=0,h=this._parts.length;ae)return r=(n-e)/i,this._map.layerPointToLatLng([s.x-r*(s.x-o.x),s.y-r*(s.y-o.y)])},getBounds:function(){return this._bounds},addLatLng:function(t,e){return e=e||this._defaultShape(),t=o.latLng(t),e.push(t),this._bounds.extend(t),this.redraw()},_setLatLngs:function(t){this._bounds=new o.LatLngBounds,this._latlngs=this._convertLatLngs(t)},_defaultShape:function(){return o.Polyline._flat(this._latlngs)?this._latlngs:this._latlngs[0]},_convertLatLngs:function(t){for(var e=[],i=o.Polyline._flat(t),n=0,s=t.length;n=2&&e[0]instanceof o.LatLng&&e[0].equals(e[i-1])&&e.pop(),e},_setLatLngs:function(t){o.Polyline.prototype._setLatLngs.call(this,t),o.Polyline._flat(this._latlngs)&&(this._latlngs=[this._latlngs])},_defaultShape:function(){return o.Polyline._flat(this._latlngs[0])?this._latlngs[0]:this._latlngs[0][0]},_clipPoints:function(){var t=this._renderer._bounds,e=this.options.weight,i=new o.Point(e,e);if(t=new o.Bounds(t.min.subtract(i),t.max.add(i)),this._parts=[],this._pxBounds&&this._pxBounds.intersects(t)){if(this.options.noClip)return void(this._parts=this._rings);for(var n,s=0,r=this._rings.length;s';var i=t.firstChild;return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(t){return!1}}(),o.SVG.include(o.Browser.vml?{_initContainer:function(){this._container=o.DomUtil.create("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(o.Renderer.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var e=t._container=o.SVG.create("shape");o.DomUtil.addClass(e,"leaflet-vml-shape "+(this.options.className||"")),e.coordsize="1 1",t._path=o.SVG.create("path"),e.appendChild(t._path),this._updateStyle(t)},_addPath:function(t){var e=t._container;this._container.appendChild(e),t.options.interactive&&t.addInteractiveTarget(e)},_removePath:function(t){var e=t._container;o.DomUtil.remove(e),t.removeInteractiveTarget(e)},_updateStyle:function(t){var e=t._stroke,i=t._fill,n=t.options,s=t._container;s.stroked=!!n.stroke,s.filled=!!n.fill,n.stroke?(e||(e=t._stroke=o.SVG.create("stroke")),s.appendChild(e),e.weight=n.weight+"px",e.color=n.color,e.opacity=n.opacity,n.dashArray?e.dashStyle=o.Util.isArray(n.dashArray)?n.dashArray.join(" "):n.dashArray.replace(/( *, *)/g," "):e.dashStyle="",e.endcap=n.lineCap.replace("butt","flat"),e.joinstyle=n.lineJoin):e&&(s.removeChild(e),t._stroke=null),n.fill?(i||(i=t._fill=o.SVG.create("fill")),s.appendChild(i),i.color=n.fillColor||n.color,i.opacity=n.fillOpacity):i&&(s.removeChild(i),t._fill=null)},_updateCircle:function(t){var e=t._point.round(),i=Math.round(t._radius),n=Math.round(t._radiusY||i);this._setPath(t,t._empty()?"M0 0":"AL "+e.x+","+e.y+" "+i+","+n+" 0,23592600")},_setPath:function(t,e){t._path.v=e},_bringToFront:function(t){o.DomUtil.toFront(t._container)},_bringToBack:function(t){o.DomUtil.toBack(t._container)}}:{}),o.Browser.vml&&(o.SVG.create=function(){try{return e.namespaces.add("lvml","urn:schemas-microsoft-com:vml"),function(t){return e.createElement("')}}catch(t){return function(t){return e.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}()),o.Canvas=o.Renderer.extend({onAdd:function(){o.Renderer.prototype.onAdd.call(this),this._draw()},_initContainer:function(){var t=this._container=e.createElement("canvas");o.DomEvent.on(t,"mousemove",o.Util.throttle(this._onMouseMove,32,this),this).on(t,"click dblclick mousedown mouseup contextmenu",this._onClick,this).on(t,"mouseout",this._handleMouseOut,this),this._ctx=t.getContext("2d")},_updatePaths:function(){var t;this._redrawBounds=null;for(var e in this._layers)t=this._layers[e],t._update();this._redraw()},_update:function(){if(!this._map._animatingZoom||!this._bounds){this._drawnLayers={},o.Renderer.prototype._update.call(this);var t=this._bounds,e=this._container,i=t.getSize(),n=o.Browser.retina?2:1;o.DomUtil.setPosition(e,t.min),e.width=n*i.x,e.height=n*i.y,e.style.width=i.x+"px",e.style.height=i.y+"px",o.Browser.retina&&this._ctx.scale(2,2),this._ctx.translate(-t.min.x,-t.min.y),this.fire("update")}},_initPath:function(t){this._updateDashArray(t),this._layers[o.stamp(t)]=t;var e=t._order={layer:t,prev:this._drawLast,next:null};this._drawLast&&(this._drawLast.next=e),this._drawLast=e,this._drawFirst=this._drawFirst||this._drawLast},_addPath:function(t){this._requestRedraw(t)},_removePath:function(t){var e=t._order,i=e.next,n=e.prev;i?i.prev=n:this._drawLast=n,n?n.next=i:this._drawFirst=i,delete t._order,delete this._layers[o.stamp(t)],this._requestRedraw(t)},_updatePath:function(t){this._extendRedrawBounds(t),t._project(),t._update(),this._requestRedraw(t)},_updateStyle:function(t){this._updateDashArray(t),this._requestRedraw(t)},_updateDashArray:function(t){if(t.options.dashArray){var e,i=t.options.dashArray.split(","),n=[];for(e=0;et.y!=n.y>t.y&&t.x<(n.x-i.x)*(t.y-i.y)/(n.y-i.y)+i.x&&(u=!u);return u||o.Polyline.prototype._containsPoint.call(this,t,!0)},o.CircleMarker.prototype._containsPoint=function(t){return t.distanceTo(this._point)<=this._radius+this._clickTolerance()},o.GeoJSON=o.FeatureGroup.extend({initialize:function(t,e){o.setOptions(this,e),this._layers={},t&&this.addData(t)},addData:function(t){var e,i,n,s=o.Util.isArray(t)?t:t.features;if(s){for(e=0,i=s.length;e1)return void(this._moved=!0);var n=i.touches&&1===i.touches.length?i.touches[0]:i,s=new o.Point(n.clientX,n.clientY),r=s.subtract(this._startPoint);(r.x||r.y)&&(Math.abs(r.x)+Math.abs(r.y)50&&(this._positions.shift(),this._times.shift())}this._map.fire("move",t).fire("drag",t)},_onZoomEnd:function(){var t=this._map.getSize().divideBy(2),e=this._map.latLngToLayerPoint([0,0]);this._initialWorldOffset=e.subtract(t).x,this._worldWidth=this._map.getPixelWorldBounds().getSize().x},_viscousLimit:function(t,e){return t-(t-e)*this._viscosity},_onPreDragLimit:function(){if(this._viscosity&&this._offsetLimit){var t=this._draggable._newPos.subtract(this._draggable._startPos),e=this._offsetLimit;t.xe.max.x&&(t.x=this._viscousLimit(t.x,e.max.x)),t.y>e.max.y&&(t.y=this._viscousLimit(t.y,e.max.y)),this._draggable._newPos=this._draggable._startPos.add(t)}},_onPreDragWrap:function(){var t=this._worldWidth,e=Math.round(t/2),i=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-e+i)%t+e-i,s=(n+e+i)%t-e-i,r=Math.abs(o+i)0?s:-s))-e;this._delta=0,this._startTime=null,r&&("center"===t.options.scrollWheelZoom?t.setZoom(e+r):t.setZoomAround(this._lastMousePos,e+r))}}),o.Map.addInitHook("addHandler","scrollWheelZoom",o.Map.ScrollWheelZoom),o.extend(o.DomEvent,{_touchstart:o.Browser.msPointer?"MSPointerDown":o.Browser.pointer?"pointerdown":"touchstart",_touchend:o.Browser.msPointer?"MSPointerUp":o.Browser.pointer?"pointerup":"touchend",addDoubleTapListener:function(t,e,i){function n(t){var e;if(e=o.Browser.pointer?o.DomEvent._pointersCount:t.touches.length,!(e>1)){var i=Date.now(),n=i-(r||i);a=t.touches?t.touches[0]:t,h=n>0&&n<=l,r=i}}function s(){if(h&&!a.cancelBubble){if(o.Browser.pointer){var t,i,n={};for(i in a)t=a[i],n[i]=t&&t.bind?t.bind(a):t;a=n}a.type="dblclick",e(a),r=null}}var r,a,h=!1,l=250,u="_leaflet_",c=this._touchstart,d=this._touchend;return t[u+c+i]=n,t[u+d+i]=s,t[u+"dblclick"+i]=e,t.addEventListener(c,n,!1),t.addEventListener(d,s,!1),o.Browser.edge||t.addEventListener("dblclick",e,!1),this},removeDoubleTapListener:function(t,e){var i="_leaflet_",n=t[i+this._touchstart+e],s=t[i+this._touchend+e],r=t[i+"dblclick"+e];return t.removeEventListener(this._touchstart,n,!1),t.removeEventListener(this._touchend,s,!1),o.Browser.edge||t.removeEventListener("dblclick",r,!1),this}}),o.extend(o.DomEvent,{POINTER_DOWN:o.Browser.msPointer?"MSPointerDown":"pointerdown",POINTER_MOVE:o.Browser.msPointer?"MSPointerMove":"pointermove",POINTER_UP:o.Browser.msPointer?"MSPointerUp":"pointerup",POINTER_CANCEL:o.Browser.msPointer?"MSPointerCancel":"pointercancel",TAG_WHITE_LIST:["INPUT","SELECT","OPTION"],_pointers:{},_pointersCount:0,addPointerListener:function(t,e,i,n){return"touchstart"===e?this._addPointerStart(t,i,n):"touchmove"===e?this._addPointerMove(t,i,n):"touchend"===e&&this._addPointerEnd(t,i,n),this},removePointerListener:function(t,e,i){var n=t["_leaflet_"+e+i];return"touchstart"===e?t.removeEventListener(this.POINTER_DOWN,n,!1):"touchmove"===e?t.removeEventListener(this.POINTER_MOVE,n,!1):"touchend"===e&&(t.removeEventListener(this.POINTER_UP,n,!1),t.removeEventListener(this.POINTER_CANCEL,n,!1)),this},_addPointerStart:function(t,i,n){var s=o.bind(function(t){if("mouse"!==t.pointerType&&t.pointerType!==t.MSPOINTER_TYPE_MOUSE){if(!(this.TAG_WHITE_LIST.indexOf(t.target.tagName)<0))return;o.DomEvent.preventDefault(t)}this._handlePointer(t,i)},this);if(t["_leaflet_touchstart"+n]=s,t.addEventListener(this.POINTER_DOWN,s,!1),!this._pointerDocListener){var r=o.bind(this._globalPointerUp,this);e.documentElement.addEventListener(this.POINTER_DOWN,o.bind(this._globalPointerDown,this),!0),e.documentElement.addEventListener(this.POINTER_MOVE,o.bind(this._globalPointerMove,this),!0),e.documentElement.addEventListener(this.POINTER_UP,r,!0),e.documentElement.addEventListener(this.POINTER_CANCEL,r,!0),this._pointerDocListener=!0}},_globalPointerDown:function(t){this._pointers[t.pointerId]=t,this._pointersCount++},_globalPointerMove:function(t){this._pointers[t.pointerId]&&(this._pointers[t.pointerId]=t)},_globalPointerUp:function(t){delete this._pointers[t.pointerId],this._pointersCount--},_handlePointer:function(t,e){t.touches=[];for(var i in this._pointers)t.touches.push(this._pointers[i]);t.changedTouches=[t],e(t)},_addPointerMove:function(t,e,i){var n=o.bind(function(t){(t.pointerType!==t.MSPOINTER_TYPE_MOUSE&&"mouse"!==t.pointerType||0!==t.buttons)&&this._handlePointer(t,e)},this);t["_leaflet_touchmove"+i]=n,t.addEventListener(this.POINTER_MOVE,n,!1)},_addPointerEnd:function(t,e,i){var n=o.bind(function(t){this._handlePointer(t,e)},this);t["_leaflet_touchend"+i]=n,t.addEventListener(this.POINTER_UP,n,!1),t.addEventListener(this.POINTER_CANCEL,n,!1)}}),o.Map.mergeOptions({touchZoom:o.Browser.touch&&!o.Browser.android23,bounceAtZoomLimits:!0}),o.Map.TouchZoom=o.Handler.extend({addHooks:function(){o.DomUtil.addClass(this._map._container,"leaflet-touch-zoom"),o.DomEvent.on(this._map._container,"touchstart",this._onTouchStart,this)},removeHooks:function(){o.DomUtil.removeClass(this._map._container,"leaflet-touch-zoom"),o.DomEvent.off(this._map._container,"touchstart",this._onTouchStart,this)},_onTouchStart:function(t){var i=this._map;if(t.touches&&2===t.touches.length&&!i._animatingZoom&&!this._zooming){var n=i.mouseEventToContainerPoint(t.touches[0]),s=i.mouseEventToContainerPoint(t.touches[1]);this._centerPoint=i.getSize()._divideBy(2),this._startLatLng=i.containerPointToLatLng(this._centerPoint),"center"!==i.options.touchZoom&&(this._pinchStartLatLng=i.containerPointToLatLng(n.add(s)._divideBy(2))),this._startDist=n.distanceTo(s),this._startZoom=i.getZoom(),this._moved=!1,this._zooming=!0,i._stop(),o.DomEvent.on(e,"touchmove",this._onTouchMove,this).on(e,"touchend",this._onTouchEnd,this),o.DomEvent.preventDefault(t)}},_onTouchMove:function(t){if(t.touches&&2===t.touches.length&&this._zooming){var e=this._map,i=e.mouseEventToContainerPoint(t.touches[0]),n=e.mouseEventToContainerPoint(t.touches[1]),s=i.distanceTo(n)/this._startDist;if(this._zoom=e.getScaleZoom(s,this._startZoom),!e.options.bounceAtZoomLimits&&(this._zoome.getMaxZoom()&&s>1)&&(this._zoom=e._limitZoom(this._zoom)),"center"===e.options.touchZoom){if(this._center=this._startLatLng,1===s)return}else{var r=i._add(n)._divideBy(2)._subtract(this._centerPoint);if(1===s&&0===r.x&&0===r.y)return;this._center=e.unproject(e.project(this._pinchStartLatLng,this._zoom).subtract(r),this._zoom)}this._moved||(e._moveStart(!0),this._moved=!0),o.Util.cancelAnimFrame(this._animRequest);var a=o.bind(e._move,e,this._center,this._zoom,{pinch:!0,round:!1});this._animRequest=o.Util.requestAnimFrame(a,this,!0),o.DomEvent.preventDefault(t)}},_onTouchEnd:function(){return this._moved&&this._zooming?(this._zooming=!1,o.Util.cancelAnimFrame(this._animRequest),o.DomEvent.off(e,"touchmove",this._onTouchMove).off(e,"touchend",this._onTouchEnd),void(this._map.options.zoomAnimation?this._map._animateZoom(this._center,this._map._limitZoom(this._zoom),!0,this._map.options.zoomSnap):this._map._resetView(this._center,this._map._limitZoom(this._zoom)))):void(this._zooming=!1)}}),o.Map.addInitHook("addHandler","touchZoom",o.Map.TouchZoom),o.Map.mergeOptions({tap:!0,tapTolerance:15}),o.Map.Tap=o.Handler.extend({addHooks:function(){o.DomEvent.on(this._map._container,"touchstart",this._onDown,this)},removeHooks:function(){o.DomEvent.off(this._map._container,"touchstart",this._onDown,this)},_onDown:function(t){if(t.touches){if(o.DomEvent.preventDefault(t),this._fireClick=!0,t.touches.length>1)return this._fireClick=!1,void clearTimeout(this._holdTimeout);var i=t.touches[0],n=i.target;this._startPos=this._newPos=new o.Point(i.clientX,i.clientY),n.tagName&&"a"===n.tagName.toLowerCase()&&o.DomUtil.addClass(n,"leaflet-active"),this._holdTimeout=setTimeout(o.bind(function(){this._isTapValid()&&(this._fireClick=!1,this._onUp(),this._simulateEvent("contextmenu",i))},this),1e3),this._simulateEvent("mousedown",i),o.DomEvent.on(e,{touchmove:this._onMove,touchend:this._onUp},this)}},_onUp:function(t){if(clearTimeout(this._holdTimeout),o.DomEvent.off(e,{touchmove:this._onMove,touchend:this._onUp},this),this._fireClick&&t&&t.changedTouches){var i=t.changedTouches[0],n=i.target;n&&n.tagName&&"a"===n.tagName.toLowerCase()&&o.DomUtil.removeClass(n,"leaflet-active"),this._simulateEvent("mouseup",i),this._isTapValid()&&this._simulateEvent("click",i)}},_isTapValid:function(){return this._newPos.distanceTo(this._startPos)<=this._map.options.tapTolerance},_onMove:function(t){var e=t.touches[0];this._newPos=new o.Point(e.clientX,e.clientY),this._simulateEvent("mousemove",e)},_simulateEvent:function(i,n){var o=e.createEvent("MouseEvents");o._simulated=!0,n.target._simulatedClick=!0,o.initMouseEvent(i,!0,!0,t,1,n.screenX,n.screenY,n.clientX,n.clientY,!1,!1,!1,!1,0,null),n.target.dispatchEvent(o)}}),o.Browser.touch&&!o.Browser.pointer&&o.Map.addInitHook("addHandler","tap",o.Map.Tap),o.Map.mergeOptions({boxZoom:!0}),o.Map.BoxZoom=o.Handler.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane},addHooks:function(){o.DomEvent.on(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){o.DomEvent.off(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_resetState:function(){this._moved=!1},_onMouseDown:function(t){ -return!(!t.shiftKey||1!==t.which&&1!==t.button)&&(this._resetState(),o.DomUtil.disableTextSelection(),o.DomUtil.disableImageDrag(),this._startPoint=this._map.mouseEventToContainerPoint(t),void o.DomEvent.on(e,{contextmenu:o.DomEvent.stop,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this))},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=o.DomUtil.create("div","leaflet-zoom-box",this._container),o.DomUtil.addClass(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var e=new o.Bounds(this._point,this._startPoint),i=e.getSize();o.DomUtil.setPosition(this._box,e.min),this._box.style.width=i.x+"px",this._box.style.height=i.y+"px"},_finish:function(){this._moved&&(o.DomUtil.remove(this._box),o.DomUtil.removeClass(this._container,"leaflet-crosshair")),o.DomUtil.enableTextSelection(),o.DomUtil.enableImageDrag(),o.DomEvent.off(e,{contextmenu:o.DomEvent.stop,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){if((1===t.which||1===t.button)&&(this._finish(),this._moved)){setTimeout(o.bind(this._resetState,this),0);var e=new o.LatLngBounds(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point));this._map.fitBounds(e).fire("boxzoomend",{boxZoomBounds:e})}},_onKeyDown:function(t){27===t.keyCode&&this._finish()}}),o.Map.addInitHook("addHandler","boxZoom",o.Map.BoxZoom),o.Map.mergeOptions({keyboard:!0,keyboardPanDelta:80}),o.Map.Keyboard=o.Handler.extend({keyCodes:{left:[37],right:[39],down:[40],up:[38],zoomIn:[187,107,61,171],zoomOut:[189,109,54,173]},initialize:function(t){this._map=t,this._setPanDelta(t.options.keyboardPanDelta),this._setZoomDelta(t.options.zoomDelta)},addHooks:function(){var t=this._map._container;t.tabIndex<=0&&(t.tabIndex="0"),o.DomEvent.on(t,{focus:this._onFocus,blur:this._onBlur,mousedown:this._onMouseDown},this),this._map.on({focus:this._addHooks,blur:this._removeHooks},this)},removeHooks:function(){this._removeHooks(),o.DomEvent.off(this._map._container,{focus:this._onFocus,blur:this._onBlur,mousedown:this._onMouseDown},this),this._map.off({focus:this._addHooks,blur:this._removeHooks},this)},_onMouseDown:function(){if(!this._focused){var i=e.body,n=e.documentElement,o=i.scrollTop||n.scrollTop,s=i.scrollLeft||n.scrollLeft;this._map._container.focus(),t.scrollTo(s,o)}},_onFocus:function(){this._focused=!0,this._map.fire("focus")},_onBlur:function(){this._focused=!1,this._map.fire("blur")},_setPanDelta:function(t){var e,i,n=this._panKeys={},o=this.keyCodes;for(e=0,i=o.left.length;e0&&t.screenY>0&&this._map.getContainer().focus()}}),o.control=function(t){return new o.Control(t)},o.Map.include({addControl:function(t){return t.addTo(this),this},removeControl:function(t){return t.remove(),this},_initControlPos:function(){function t(t,s){var r=i+t+" "+i+s;e[t+s]=o.DomUtil.create("div",r,n)}var e=this._controlCorners={},i="leaflet-",n=this._controlContainer=o.DomUtil.create("div",i+"control-container",this._container);t("top","left"),t("top","right"),t("bottom","left"),t("bottom","right")},_clearControlPos:function(){o.DomUtil.remove(this._controlContainer)}}),o.Control.Zoom=o.Control.extend({options:{position:"topleft",zoomInText:"+",zoomInTitle:"Zoom in",zoomOutText:"-",zoomOutTitle:"Zoom out"},onAdd:function(t){var e="leaflet-control-zoom",i=o.DomUtil.create("div",e+" leaflet-bar"),n=this.options;return this._zoomInButton=this._createButton(n.zoomInText,n.zoomInTitle,e+"-in",i,this._zoomIn),this._zoomOutButton=this._createButton(n.zoomOutText,n.zoomOutTitle,e+"-out",i,this._zoomOut),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),i},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoomthis._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,e,i,n,s){var r=o.DomUtil.create("a",i,n);return r.innerHTML=t,r.href="#",r.title=e,r.setAttribute("role","button"),r.setAttribute("aria-label",e),o.DomEvent.on(r,"mousedown dblclick",o.DomEvent.stopPropagation).on(r,"click",o.DomEvent.stop).on(r,"click",s,this).on(r,"click",this._refocusOnMap,this),r},_updateDisabled:function(){var t=this._map,e="leaflet-disabled";o.DomUtil.removeClass(this._zoomInButton,e),o.DomUtil.removeClass(this._zoomOutButton,e),(this._disabled||t._zoom===t.getMinZoom())&&o.DomUtil.addClass(this._zoomOutButton,e),(this._disabled||t._zoom===t.getMaxZoom())&&o.DomUtil.addClass(this._zoomInButton,e)}}),o.Map.mergeOptions({zoomControl:!0}),o.Map.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new o.Control.Zoom,this.addControl(this.zoomControl))}),o.control.zoom=function(t){return new o.Control.Zoom(t)},o.Control.Attribution=o.Control.extend({options:{position:"bottomright",prefix:'Leaflet'},initialize:function(t){o.setOptions(this,t),this._attributions={}},onAdd:function(t){t.attributionControl=this,this._container=o.DomUtil.create("div","leaflet-control-attribution"),o.DomEvent&&o.DomEvent.disableClickPropagation(this._container);for(var e in t._layers)t._layers[e].getAttribution&&this.addAttribution(t._layers[e].getAttribution());return this._update(),this._container},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t?(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update(),this):this},removeAttribution:function(t){return t?(this._attributions[t]&&(this._attributions[t]--,this._update()),this):this},_update:function(){if(this._map){var t=[];for(var e in this._attributions)this._attributions[e]&&t.push(e);var i=[];this.options.prefix&&i.push(this.options.prefix),t.length&&i.push(t.join(", ")),this._container.innerHTML=i.join(" | ")}}}),o.Map.mergeOptions({attributionControl:!0}),o.Map.addInitHook(function(){this.options.attributionControl&&(new o.Control.Attribution).addTo(this)}),o.control.attribution=function(t){return new o.Control.Attribution(t)},o.Control.Scale=o.Control.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var e="leaflet-control-scale",i=o.DomUtil.create("div",e),n=this.options;return this._addScales(n,e+"-line",i),t.on(n.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),i},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,e,i){t.metric&&(this._mScale=o.DomUtil.create("div",e,i)),t.imperial&&(this._iScale=o.DomUtil.create("div",e,i))},_update:function(){var t=this._map,e=t.getSize().y/2,i=t.distance(t.containerPointToLatLng([0,e]),t.containerPointToLatLng([this.options.maxWidth,e]));this._updateScales(i)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var e=this._getRoundNum(t),i=e<1e3?e+" m":e/1e3+" km";this._updateScale(this._mScale,i,e/t)},_updateImperial:function(t){var e,i,n,o=3.2808399*t;o>5280?(e=o/5280,i=this._getRoundNum(e),this._updateScale(this._iScale,i+" mi",i/e)):(n=this._getRoundNum(o),this._updateScale(this._iScale,n+" ft",n/o))},_updateScale:function(t,e,i){t.style.width=Math.round(this.options.maxWidth*i)+"px",t.innerHTML=e},_getRoundNum:function(t){var e=Math.pow(10,(Math.floor(t)+"").length-1),i=t/e;return i=i>=10?10:i>=5?5:i>=3?3:i>=2?2:1,e*i}}),o.control.scale=function(t){return new o.Control.Scale(t)},o.Control.Layers=o.Control.extend({options:{collapsed:!0,position:"topright",autoZIndex:!0,hideSingleBase:!1,sortLayers:!1,sortFunction:function(t,e,i,n){return i1,this._baseLayersList.style.display=t?"":"none"),this._separator.style.display=e&&t?"":"none",this},_onLayerChange:function(t){this._handlingClick||this._update();var e=this._getLayer(o.stamp(t.target)),i=e.overlay?"add"===t.type?"overlayadd":"overlayremove":"add"===t.type?"baselayerchange":null;i&&this._map.fire(i,e)},_createRadioElement:function(t,i){var n='",o=e.createElement("div");return o.innerHTML=n,o.firstChild},_addItem:function(t){var i,n=e.createElement("label"),s=this._map.hasLayer(t.layer);t.overlay?(i=e.createElement("input"),i.type="checkbox",i.className="leaflet-control-layers-selector",i.defaultChecked=s):i=this._createRadioElement("leaflet-base-layers",s),i.layerId=o.stamp(t.layer),o.DomEvent.on(i,"click",this._onInputClick,this);var r=e.createElement("span");r.innerHTML=" "+t.name;var a=e.createElement("div");n.appendChild(a),a.appendChild(i),a.appendChild(r);var h=t.overlay?this._overlaysList:this._baseLayersList;return h.appendChild(n),this._checkDisabledLayers(),n},_onInputClick:function(){var t,e,i,n=this._form.getElementsByTagName("input"),o=[],s=[];this._handlingClick=!0;for(var r=n.length-1;r>=0;r--)t=n[r],e=this._getLayer(t.layerId).layer,i=this._map.hasLayer(e),t.checked&&!i?o.push(e):!t.checked&&i&&s.push(e);for(r=0;r=0;s--)t=n[s],e=this._getLayer(t.layerId).layer,t.disabled=e.options.minZoom!==i&&oe.options.maxZoom},_expand:function(){return this.expand()},_collapse:function(){return this.collapse()}}),o.control.layers=function(t,e,i){return new o.Control.Layers(t,e,i)}}(window,document); \ No newline at end of file +/* @preserve + * Leaflet 1.6.0+Detached: 0c81bdf904d864fd12a286e3d1979f47aba17991.0c81bdf, a JS library for interactive maps. http://leafletjs.com + * (c) 2010-2019 Vladimir Agafonkin, (c) 2010-2011 CloudMade + */ +!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?i(exports):"function"==typeof define&&define.amd?define(["exports"],i):i(t.L={})}(this,function(t){"use strict";var i=Object.freeze;function h(t){var i,e,n,o;for(e=1,n=arguments.length;e=this.min.x&&e.x<=this.max.x&&i.y>=this.min.y&&e.y<=this.max.y},intersects:function(t){t=R(t);var i=this.min,e=this.max,n=t.min,o=t.max,s=o.x>=i.x&&n.x<=e.x,r=o.y>=i.y&&n.y<=e.y;return s&&r},overlaps:function(t){t=R(t);var i=this.min,e=this.max,n=t.min,o=t.max,s=o.x>i.x&&n.xi.y&&n.y=n.lat&&e.lat<=o.lat&&i.lng>=n.lng&&e.lng<=o.lng},intersects:function(t){t=D(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),o=t.getNorthEast(),s=o.lat>=i.lat&&n.lat<=e.lat,r=o.lng>=i.lng&&n.lng<=e.lng;return s&&r},overlaps:function(t){t=D(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),o=t.getNorthEast(),s=o.lat>i.lat&&n.lati.lng&&n.lng';var i=t.firstChild;return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(t){return!1}}();function Bt(t){return 0<=navigator.userAgent.toLowerCase().indexOf(t)}var At=(Object.freeze||Object)({ie:it,ielt9:et,edge:nt,webkit:ot,android:st,android23:rt,androidStock:ht,opera:ut,chrome:lt,gecko:ct,safari:_t,phantom:dt,opera12:pt,win:mt,ie3d:ft,webkit3d:gt,gecko3d:vt,any3d:yt,mobile:xt,mobileWebkit:wt,mobileWebkit3d:Pt,msPointer:Lt,pointer:bt,touch:Tt,mobileOpera:zt,mobileGecko:Mt,retina:Ct,passiveEvents:Et,canvas:St,svg:Zt,vml:kt}),It=Lt?"MSPointerDown":"pointerdown",Ot=Lt?"MSPointerMove":"pointermove",Rt=Lt?"MSPointerUp":"pointerup",Nt=Lt?"MSPointerCancel":"pointercancel",Dt=["INPUT","SELECT","OPTION"],jt={},Wt=!1,Ht=0;function Ft(t,i,e,n){return"touchstart"===i?function(t,i,e){var n=a(function(t){if("mouse"!==t.pointerType&&t.MSPOINTER_TYPE_MOUSE&&t.pointerType!==t.MSPOINTER_TYPE_MOUSE){if(!(Dt.indexOf(t.target.tagName)<0))return;ji(t)}Gt(t,i)});t["_leaflet_touchstart"+e]=n,t.addEventListener(It,n,!1),Wt||(document.documentElement.addEventListener(It,Ut,!0),document.documentElement.addEventListener(Ot,Vt,!0),document.documentElement.addEventListener(Rt,qt,!0),document.documentElement.addEventListener(Nt,qt,!0),Wt=!0)}(t,e,n):"touchmove"===i?function(t,i,e){function n(t){(t.pointerType!==t.MSPOINTER_TYPE_MOUSE&&"mouse"!==t.pointerType||0!==t.buttons)&&Gt(t,i)}t["_leaflet_touchmove"+e]=n,t.addEventListener(Ot,n,!1)}(t,e,n):"touchend"===i&&function(t,i,e){function n(t){Gt(t,i)}t["_leaflet_touchend"+e]=n,t.addEventListener(Rt,n,!1),t.addEventListener(Nt,n,!1)}(t,e,n),this}function Ut(t){jt[t.pointerId]=t,Ht++}function Vt(t){jt[t.pointerId]&&(jt[t.pointerId]=t)}function qt(t){delete jt[t.pointerId],Ht--}function Gt(t,i){for(var e in t.touches=[],jt)t.touches.push(jt[e]);t.changedTouches=[t],i(t)}var Kt=Lt?"MSPointerDown":bt?"pointerdown":"touchstart",Yt=Lt?"MSPointerUp":bt?"pointerup":"touchend",Xt="_leaflet_";function Jt(t,o,i){var s,r,a=!1;function e(t){var i;if(bt){if(!nt||"mouse"===t.pointerType)return;i=Ht}else i=t.touches.length;if(!(1this.options.maxZoom)?this.setZoom(t):this},panInsideBounds:function(t,i){this._enforcingBounds=!0;var e=this.getCenter(),n=this._limitCenter(e,this._zoom,D(t));return e.equals(n)||this.panTo(n,i),this._enforcingBounds=!1,this},panInside:function(t,i){var e=I((i=i||{}).paddingTopLeft||i.padding||[0,0]),n=I(i.paddingBottomRight||i.padding||[0,0]),o=this.getCenter(),s=this.project(o),r=this.project(t),a=this.getPixelBounds(),h=a.getSize().divideBy(2),u=R([a.min.add(e),a.max.subtract(n)]);if(!u.contains(r)){this._enforcingBounds=!0;var l=s.subtract(r),c=I(r.x+l.x,r.y+l.y);(r.xu.max.x)&&(c.x=s.x-l.x,0u.max.y)&&(c.y=s.y-l.y,0=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,i){for(var e,n=[],o="mouseout"===i||"mouseover"===i,s=t.target||t.srcElement,r=!1;s;){if((e=this._targets[u(s)])&&("click"===i||"preclick"===i)&&!t._simulated&&this._draggableMoved(e)){r=!0;break}if(e&&e.listens(i,!0)){if(o&&!Yi(s,t))break;if(n.push(e),o)break}if(s===this._container)break;s=s.parentNode}return n.length||r||o||!Yi(s,t)||(n=[this]),n},_handleDOMEvent:function(t){if(this._loaded&&!Ki(t)){var i=t.type;"mousedown"!==i&&"keypress"!==i&&"keyup"!==i&&"keydown"!==i||Mi(t.target||t.srcElement),this._fireDOMEvent(t,i)}},_mouseEvents:["click","dblclick","mouseover","mouseout","contextmenu"],_fireDOMEvent:function(t,i,e){if("click"===t.type){var n=h({},t);n.type="preclick",this._fireDOMEvent(n,n.type,e)}if(!t._stopped&&(e=(e||[]).concat(this._findEventTargets(t,i))).length){var o=e[0];"contextmenu"===i&&o.listens(i,!0)&&ji(t);var s={originalEvent:t};if("keypress"!==t.type&&"keydown"!==t.type&&"keyup"!==t.type){var r=o.getLatLng&&(!o._radius||o._radius<=10);s.containerPoint=r?this.latLngToContainerPoint(o.getLatLng()):this.mouseEventToContainerPoint(t),s.layerPoint=this.containerPointToLayerPoint(s.containerPoint),s.latlng=r?o.getLatLng():this.layerPointToLatLng(s.layerPoint)}for(var a=0;athis.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(i),o=this._getCenterOffset(t)._divideBy(1-1/n);return!(!0!==e.animate&&!this.getSize().contains(o))&&(M(function(){this._moveStart(!0,!1)._animateZoom(t,i,!0)},this),!0)},_animateZoom:function(t,i,e,n){this._mapPane&&(e&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=i,mi(this._mapPane,"leaflet-zoom-anim")),this.fire("zoomanim",{center:t,zoom:i,noUpdate:n}),setTimeout(a(this._onZoomTransitionEnd,this),250))},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._mapPane&&fi(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom),M(function(){this._moveEnd(!0)},this))}});function Qi(t){return new te(t)}var te=S.extend({options:{position:"topright"},initialize:function(t){p(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var i=this._map;return i&&i.removeControl(this),this.options.position=t,i&&i.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this.remove(),this._map=t;var i=this._container=this.onAdd(t),e=this.getPosition(),n=t._controlCorners[e];return mi(i,"leaflet-control"),-1!==e.indexOf("bottom")?n.insertBefore(i,n.firstChild):n.appendChild(i),this._map.on("unload",this.remove,this),this},remove:function(){return this._map&&(li(this._container),this.onRemove&&this.onRemove(this._map),this._map.off("unload",this.remove,this),this._map=null),this},_refocusOnMap:function(t){this._map&&t&&0",n=document.createElement("div");return n.innerHTML=e,n.firstChild},_addItem:function(t){var i,e=document.createElement("label"),n=this._map.hasLayer(t.layer);t.overlay?((i=document.createElement("input")).type="checkbox",i.className="leaflet-control-layers-selector",i.defaultChecked=n):i=this._createRadioElement("leaflet-base-layers_"+u(this),n),this._layerControlInputs.push(i),i.layerId=u(t.layer),ki(i,"click",this._onInputClick,this);var o=document.createElement("span");o.innerHTML=" "+t.name;var s=document.createElement("div");return e.appendChild(s),s.appendChild(i),s.appendChild(o),(t.overlay?this._overlaysList:this._baseLayersList).appendChild(e),this._checkDisabledLayers(),e},_onInputClick:function(){var t,i,e=this._layerControlInputs,n=[],o=[];this._handlingClick=!0;for(var s=e.length-1;0<=s;s--)t=e[s],i=this._getLayer(t.layerId).layer,t.checked?n.push(i):t.checked||o.push(i);for(s=0;si.options.maxZoom},_expandIfNotCollapsed:function(){return this._map&&!this.options.collapsed&&this.expand(),this},_expand:function(){return this.expand()},_collapse:function(){return this.collapse()}}),ee=te.extend({options:{position:"topleft",zoomInText:"+",zoomInTitle:"Zoom in",zoomOutText:"−",zoomOutTitle:"Zoom out"},onAdd:function(t){var i="leaflet-control-zoom",e=ui("div",i+" leaflet-bar"),n=this.options;return this._zoomInButton=this._createButton(n.zoomInText,n.zoomInTitle,i+"-in",e,this._zoomIn),this._zoomOutButton=this._createButton(n.zoomOutText,n.zoomOutTitle,i+"-out",e,this._zoomOut),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),e},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoomthis._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,i,e,n,o){var s=ui("a",e,n);return s.innerHTML=t,s.href="#",s.title=i,s.setAttribute("role","button"),s.setAttribute("aria-label",i),Di(s),ki(s,"click",Wi),ki(s,"click",o,this),ki(s,"click",this._refocusOnMap,this),s},_updateDisabled:function(){var t=this._map,i="leaflet-disabled";fi(this._zoomInButton,i),fi(this._zoomOutButton,i),!this._disabled&&t._zoom!==t.getMinZoom()||mi(this._zoomOutButton,i),!this._disabled&&t._zoom!==t.getMaxZoom()||mi(this._zoomInButton,i)}});$i.mergeOptions({zoomControl:!0}),$i.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new ee,this.addControl(this.zoomControl))});var ne=te.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var i="leaflet-control-scale",e=ui("div",i),n=this.options;return this._addScales(n,i+"-line",e),t.on(n.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),e},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,i,e){t.metric&&(this._mScale=ui("div",i,e)),t.imperial&&(this._iScale=ui("div",i,e))},_update:function(){var t=this._map,i=t.getSize().y/2,e=t.distance(t.containerPointToLatLng([0,i]),t.containerPointToLatLng([this.options.maxWidth,i]));this._updateScales(e)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var i=this._getRoundNum(t),e=i<1e3?i+" m":i/1e3+" km";this._updateScale(this._mScale,e,i/t)},_updateImperial:function(t){var i,e,n,o=3.2808399*t;5280Leaflet'},initialize:function(t){p(this,t),this._attributions={}},onAdd:function(t){for(var i in(t.attributionControl=this)._container=ui("div","leaflet-control-attribution"),Di(this._container),t._layers)t._layers[i].getAttribution&&this.addAttribution(t._layers[i].getAttribution());return this._update(),this._container},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t&&(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update()),this},removeAttribution:function(t){return t&&this._attributions[t]&&(this._attributions[t]--,this._update()),this},_update:function(){if(this._map){var t=[];for(var i in this._attributions)this._attributions[i]&&t.push(i);var e=[];this.options.prefix&&e.push(this.options.prefix),t.length&&e.push(t.join(", ")),this._container.innerHTML=e.join(" | ")}}});$i.mergeOptions({attributionControl:!0}),$i.addInitHook(function(){this.options.attributionControl&&(new oe).addTo(this)});te.Layers=ie,te.Zoom=ee,te.Scale=ne,te.Attribution=oe,Qi.layers=function(t,i,e){return new ie(t,i,e)},Qi.zoom=function(t){return new ee(t)},Qi.scale=function(t){return new ne(t)},Qi.attribution=function(t){return new oe(t)};var se=S.extend({initialize:function(t){this._map=t},enable:function(){return this._enabled||(this._enabled=!0,this.addHooks()),this},disable:function(){return this._enabled&&(this._enabled=!1,this.removeHooks()),this},enabled:function(){return!!this._enabled}});se.addTo=function(t,i){return t.addHandler(i,this),this};var re,ae={Events:Z},he=Tt?"touchstart mousedown":"mousedown",ue={mousedown:"mouseup",touchstart:"touchend",pointerdown:"touchend",MSPointerDown:"touchend"},le={mousedown:"mousemove",touchstart:"touchmove",pointerdown:"touchmove",MSPointerDown:"touchmove"},ce=k.extend({options:{clickTolerance:3},initialize:function(t,i,e,n){p(this,n),this._element=t,this._dragStartTarget=i||t,this._preventOutline=e},enable:function(){this._enabled||(ki(this._dragStartTarget,he,this._onDown,this),this._enabled=!0)},disable:function(){this._enabled&&(ce._dragging===this&&this.finishDrag(),Ai(this._dragStartTarget,he,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(t){if(!t._simulated&&this._enabled&&(this._moved=!1,!pi(this._element,"leaflet-zoom-anim")&&!(ce._dragging||t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||((ce._dragging=this)._preventOutline&&Mi(this._element),Ti(),Qt(),this._moving)))){this.fire("down");var i=t.touches?t.touches[0]:t,e=Ei(this._element);this._startPoint=new B(i.clientX,i.clientY),this._parentScale=Si(e),ki(document,le[t.type],this._onMove,this),ki(document,ue[t.type],this._onUp,this)}},_onMove:function(t){if(!t._simulated&&this._enabled)if(t.touches&&1i.max.x&&(e|=2),t.yi.max.y&&(e|=8),e}function ge(t,i,e,n){var o,s=i.x,r=i.y,a=e.x-s,h=e.y-r,u=a*a+h*h;return 0this._layersMaxZoom&&this.setZoom(this._layersMaxZoom),void 0===this.options.minZoom&&this._layersMinZoom&&this.getZoom()t.y!=n.y>t.y&&t.x<(n.x-e.x)*(t.y-e.y)/(n.y-e.y)+e.x&&(u=!u);return u||je.prototype._containsPoint.call(this,t,!0)}});var He=ke.extend({initialize:function(t,i){p(this,i),this._layers={},t&&this.addData(t)},addData:function(t){var i,e,n,o=v(t)?t:t.features;if(o){for(i=0,e=o.length;iu.x&&(l=s.x+n-u.x+h.x),s.x-l-a.x<0&&(l=s.x-a.x),s.y+e+h.y>u.y&&(c=s.y+e-u.y+h.y),s.y-c-a.y<0&&(c=s.y-a.y),(l||c)&&t.fire("autopanstart").panBy([l,c])}},_onCloseButtonClick:function(t){this._close(),Wi(t)},_getAnchor:function(){return I(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}});$i.mergeOptions({closePopupOnClick:!0}),$i.include({openPopup:function(t,i,e){return t instanceof sn||(t=new sn(e).setContent(t)),i&&t.setLatLng(i),this.hasLayer(t)?this:(this._popup&&this._popup.options.autoClose&&this.closePopup(),this._popup=t,this.addLayer(t))},closePopup:function(t){return t&&t!==this._popup||(t=this._popup,this._popup=null),t&&this.removeLayer(t),this}}),Se.include({bindPopup:function(t,i){return t instanceof sn?(p(t,i),(this._popup=t)._source=this):(this._popup&&!i||(this._popup=new sn(i,this)),this._popup.setContent(t)),this._popupHandlersAdded||(this.on({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t,i){return this._popup&&this._map&&(i=this._popup._prepareOpen(this,t,i),this._map.openPopup(this._popup,i)),this},closePopup:function(){return this._popup&&this._popup._close(),this},togglePopup:function(t){return this._popup&&(this._popup._map?this.closePopup():this.openPopup(t)),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var i=t.layer||t.target;this._popup&&this._map&&(Wi(t),i instanceof Re?this.openPopup(t.layer||t.target,t.latlng):this._map.hasLayer(this._popup)&&this._popup._source===i?this.closePopup():this.openPopup(i,t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)},_onKeyPress:function(t){13===t.originalEvent.keyCode&&this._openPopup(t)}});var rn=on.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,interactive:!1,opacity:.9},onAdd:function(t){on.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&this._source.fire("tooltipopen",{tooltip:this},!0)},onRemove:function(t){on.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&this._source.fire("tooltipclose",{tooltip:this},!0)},getEvents:function(){var t=on.prototype.getEvents.call(this);return Tt&&!this.options.permanent&&(t.preclick=this._close),t},_close:function(){this._map&&this._map.closeTooltip(this)},_initLayout:function(){var t="leaflet-tooltip "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=ui("div",t)},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var i=this._map,e=this._container,n=i.latLngToContainerPoint(i.getCenter()),o=i.layerPointToContainerPoint(t),s=this.options.direction,r=e.offsetWidth,a=e.offsetHeight,h=I(this.options.offset),u=this._getAnchor();t="top"===s?t.add(I(-r/2+h.x,-a+h.y+u.y,!0)):"bottom"===s?t.subtract(I(r/2-h.x,-h.y,!0)):"center"===s?t.subtract(I(r/2+h.x,a/2-u.y+h.y,!0)):"right"===s||"auto"===s&&o.xthis.options.maxZoom||ethis.options.maxZoom||void 0!==this.options.minZoom&&oe.max.x)||!i.wrapLat&&(t.ye.max.y))return!1}if(!this.options.bounds)return!0;var n=this._tileCoordsToBounds(t);return D(this.options.bounds).overlaps(n)},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToNwSe:function(t){var i=this._map,e=this.getTileSize(),n=t.scaleBy(e),o=n.add(e);return[i.unproject(n,t.z),i.unproject(o,t.z)]},_tileCoordsToBounds:function(t){var i=this._tileCoordsToNwSe(t),e=new N(i[0],i[1]);return this.options.noWrap||(e=this._map.wrapLatLngBounds(e)),e},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var i=t.split(":"),e=new B(+i[0],+i[1]);return e.z=+i[2],e},_removeTile:function(t){var i=this._tiles[t];i&&(li(i.el),delete this._tiles[t],this.fire("tileunload",{tile:i.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){mi(t,"leaflet-tile");var i=this.getTileSize();t.style.width=i.x+"px",t.style.height=i.y+"px",t.onselectstart=l,t.onmousemove=l,et&&this.options.opacity<1&&yi(t,this.options.opacity),st&&!rt&&(t.style.WebkitBackfaceVisibility="hidden")},_addTile:function(t,i){var e=this._getTilePos(t),n=this._tileCoordsToKey(t),o=this.createTile(this._wrapCoords(t),a(this._tileReady,this,t));this._initTile(o),this.createTile.length<2&&M(a(this._tileReady,this,t,null,o)),Pi(o,e),this._tiles[n]={el:o,coords:t,current:!0},i.appendChild(o),this.fire("tileloadstart",{tile:o,coords:t})},_tileReady:function(t,i,e){i&&this.fire("tileerror",{error:i,tile:e,coords:t});var n=this._tileCoordsToKey(t);(e=this._tiles[n])&&(e.loaded=+new Date,this._map._fadeAnimated?(yi(e.el,0),C(this._fadeFrame),this._fadeFrame=M(this._updateOpacity,this)):(e.active=!0,this._pruneTiles()),i||(mi(e.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:e.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),et||!this._map._fadeAnimated?M(this._pruneTiles,this):setTimeout(a(this._pruneTiles,this),250)))},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var i=new B(this._wrapX?r(t.x,this._wrapX):t.x,this._wrapY?r(t.y,this._wrapY):t.y);return i.z=t.z,i},_pxBoundsToTileRange:function(t){var i=this.getTileSize();return new O(t.min.unscaleBy(i).floor(),t.max.unscaleBy(i).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}});var un=hn.extend({options:{minZoom:0,maxZoom:18,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1},initialize:function(t,i){this._url=t,(i=p(this,i)).detectRetina&&Ct&&0')}}catch(t){return function(t){return document.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}(),fn={_initContainer:function(){this._container=ui("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(_n.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var i=t._container=mn("shape");mi(i,"leaflet-vml-shape "+(this.options.className||"")),i.coordsize="1 1",t._path=mn("path"),i.appendChild(t._path),this._updateStyle(t),this._layers[u(t)]=t},_addPath:function(t){var i=t._container;this._container.appendChild(i),t.options.interactive&&t.addInteractiveTarget(i)},_removePath:function(t){var i=t._container;li(i),t.removeInteractiveTarget(i),delete this._layers[u(t)]},_updateStyle:function(t){var i=t._stroke,e=t._fill,n=t.options,o=t._container;o.stroked=!!n.stroke,o.filled=!!n.fill,n.stroke?(i||(i=t._stroke=mn("stroke")),o.appendChild(i),i.weight=n.weight+"px",i.color=n.color,i.opacity=n.opacity,n.dashArray?i.dashStyle=v(n.dashArray)?n.dashArray.join(" "):n.dashArray.replace(/( *, *)/g," "):i.dashStyle="",i.endcap=n.lineCap.replace("butt","flat"),i.joinstyle=n.lineJoin):i&&(o.removeChild(i),t._stroke=null),n.fill?(e||(e=t._fill=mn("fill")),o.appendChild(e),e.color=n.fillColor||n.color,e.opacity=n.fillOpacity):e&&(o.removeChild(e),t._fill=null)},_updateCircle:function(t){var i=t._point.round(),e=Math.round(t._radius),n=Math.round(t._radiusY||e);this._setPath(t,t._empty()?"M0 0":"AL "+i.x+","+i.y+" "+e+","+n+" 0,23592600")},_setPath:function(t,i){t._path.v=i},_bringToFront:function(t){_i(t._container)},_bringToBack:function(t){di(t._container)}},gn=kt?mn:$,vn=_n.extend({getEvents:function(){var t=_n.prototype.getEvents.call(this);return t.zoomstart=this._onZoomStart,t},_initContainer:function(){this._container=gn("svg"),this._container.setAttribute("pointer-events","none"),this._rootGroup=gn("g"),this._container.appendChild(this._rootGroup)},_destroyContainer:function(){li(this._container),Ai(this._container),delete this._container,delete this._rootGroup,delete this._svgSize},_onZoomStart:function(){this._update()},_update:function(){if(!this._map._animatingZoom||!this._bounds){_n.prototype._update.call(this);var t=this._bounds,i=t.getSize(),e=this._container;this._svgSize&&this._svgSize.equals(i)||(this._svgSize=i,e.setAttribute("width",i.x),e.setAttribute("height",i.y)),Pi(e,t.min),e.setAttribute("viewBox",[t.min.x,t.min.y,i.x,i.y].join(" ")),this.fire("update")}},_initPath:function(t){var i=t._path=gn("path");t.options.className&&mi(i,t.options.className),t.options.interactive&&mi(i,"leaflet-interactive"),this._updateStyle(t),this._layers[u(t)]=t},_addPath:function(t){this._rootGroup||this._initContainer(),this._rootGroup.appendChild(t._path),t.addInteractiveTarget(t._path)},_removePath:function(t){li(t._path),t.removeInteractiveTarget(t._path),delete this._layers[u(t)]},_updatePath:function(t){t._project(),t._update()},_updateStyle:function(t){var i=t._path,e=t.options;i&&(e.stroke?(i.setAttribute("stroke",e.color),i.setAttribute("stroke-opacity",e.opacity),i.setAttribute("stroke-width",e.weight),i.setAttribute("stroke-linecap",e.lineCap),i.setAttribute("stroke-linejoin",e.lineJoin),e.dashArray?i.setAttribute("stroke-dasharray",e.dashArray):i.removeAttribute("stroke-dasharray"),e.dashOffset?i.setAttribute("stroke-dashoffset",e.dashOffset):i.removeAttribute("stroke-dashoffset")):i.setAttribute("stroke","none"),e.fill?(i.setAttribute("fill",e.fillColor||e.color),i.setAttribute("fill-opacity",e.fillOpacity),i.setAttribute("fill-rule",e.fillRule||"evenodd")):i.setAttribute("fill","none"))},_updatePoly:function(t,i){this._setPath(t,Q(t._parts,i))},_updateCircle:function(t){var i=t._point,e=Math.max(Math.round(t._radius),1),n="a"+e+","+(Math.max(Math.round(t._radiusY),1)||e)+" 0 1,0 ",o=t._empty()?"M0 0":"M"+(i.x-e)+","+i.y+n+2*e+",0 "+n+2*-e+",0 ";this._setPath(t,o)},_setPath:function(t,i){t._path.setAttribute("d",i)},_bringToFront:function(t){_i(t._path)},_bringToBack:function(t){di(t._path)}});function yn(t){return Zt||kt?new vn(t):null}kt&&vn.include(fn),$i.include({getRenderer:function(t){var i=t.options.renderer||this._getPaneRenderer(t.options.pane)||this.options.renderer||this._renderer;return i||(i=this._renderer=this._createRenderer()),this.hasLayer(i)||this.addLayer(i),i},_getPaneRenderer:function(t){if("overlayPane"===t||void 0===t)return!1;var i=this._paneRenderers[t];return void 0===i&&(i=this._createRenderer({pane:t}),this._paneRenderers[t]=i),i},_createRenderer:function(t){return this.options.preferCanvas&&pn(t)||yn(t)}});var xn=We.extend({initialize:function(t,i){We.prototype.initialize.call(this,this._boundsToLatLngs(t),i)},setBounds:function(t){return this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return[(t=D(t)).getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}});vn.create=gn,vn.pointsToPath=Q,He.geometryToLayer=Fe,He.coordsToLatLng=Ve,He.coordsToLatLngs=qe,He.latLngToCoords=Ge,He.latLngsToCoords=Ke,He.getFeature=Ye,He.asFeature=Xe,$i.mergeOptions({boxZoom:!0});var wn=se.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._resetStateTimeout=0,t.on("unload",this._destroy,this)},addHooks:function(){ki(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){Ai(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_destroy:function(){li(this._pane),delete this._pane},_resetState:function(){this._resetStateTimeout=0,this._moved=!1},_clearDeferredResetState:function(){0!==this._resetStateTimeout&&(clearTimeout(this._resetStateTimeout),this._resetStateTimeout=0)},_onMouseDown:function(t){if(!t.shiftKey||1!==t.which&&1!==t.button)return!1;this._clearDeferredResetState(),this._resetState(),Qt(),Ti(),this._startPoint=this._map.mouseEventToContainerPoint(t),ki(document,{contextmenu:Wi,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=ui("div","leaflet-zoom-box",this._container),mi(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var i=new O(this._point,this._startPoint),e=i.getSize();Pi(this._box,i.min),this._box.style.width=e.x+"px",this._box.style.height=e.y+"px"},_finish:function(){this._moved&&(li(this._box),fi(this._container,"leaflet-crosshair")),ti(),zi(),Ai(document,{contextmenu:Wi,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){if((1===t.which||1===t.button)&&(this._finish(),this._moved)){this._clearDeferredResetState(),this._resetStateTimeout=setTimeout(a(this._resetState,this),0);var i=new N(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point));this._map.fitBounds(i).fire("boxzoomend",{boxZoomBounds:i})}},_onKeyDown:function(t){27===t.keyCode&&this._finish()}});$i.addInitHook("addHandler","boxZoom",wn),$i.mergeOptions({doubleClickZoom:!0});var Pn=se.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var i=this._map,e=i.getZoom(),n=i.options.zoomDelta,o=t.originalEvent.shiftKey?e-n:e+n;"center"===i.options.doubleClickZoom?i.setZoom(o):i.setZoomAround(t.containerPoint,o)}});$i.addInitHook("addHandler","doubleClickZoom",Pn),$i.mergeOptions({dragging:!0,inertia:!rt,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,easeLinearity:.2,worldCopyJump:!1,maxBoundsViscosity:0});var Ln=se.extend({addHooks:function(){if(!this._draggable){var t=this._map;this._draggable=new ce(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),this._draggable.on("predrag",this._onPreDragLimit,this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDragWrap,this),t.on("zoomend",this._onZoomEnd,this),t.whenReady(this._onZoomEnd,this))}mi(this._map._container,"leaflet-grab leaflet-touch-drag"),this._draggable.enable(),this._positions=[],this._times=[]},removeHooks:function(){fi(this._map._container,"leaflet-grab"),fi(this._map._container,"leaflet-touch-drag"),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},moving:function(){return this._draggable&&this._draggable._moving},_onDragStart:function(){var t=this._map;if(t._stop(),this._map.options.maxBounds&&this._map.options.maxBoundsViscosity){var i=D(this._map.options.maxBounds);this._offsetLimit=R(this._map.latLngToContainerPoint(i.getNorthWest()).multiplyBy(-1),this._map.latLngToContainerPoint(i.getSouthEast()).multiplyBy(-1).add(this._map.getSize())),this._viscosity=Math.min(1,Math.max(0,this._map.options.maxBoundsViscosity))}else this._offsetLimit=null;t.fire("movestart").fire("dragstart"),t.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(t){if(this._map.options.inertia){var i=this._lastTime=+new Date,e=this._lastPos=this._draggable._absPos||this._draggable._newPos;this._positions.push(e),this._times.push(i),this._prunePositions(i)}this._map.fire("move",t).fire("drag",t)},_prunePositions:function(t){for(;1i.max.x&&(t.x=this._viscousLimit(t.x,i.max.x)),t.y>i.max.y&&(t.y=this._viscousLimit(t.y,i.max.y)),this._draggable._newPos=this._draggable._startPos.add(t)}},_onPreDragWrap:function(){var t=this._worldWidth,i=Math.round(t/2),e=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-i+e)%t+i-e,s=(n+i+e)%t-i-e,r=Math.abs(o+e)i.getMaxZoom()&&1 - From 79218dee46cdeac86e6f06f086d1fefa5471d48b Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Mon, 26 Jul 2021 15:43:18 +0200 Subject: [PATCH 21/30] style(species): add rgb equivalent primary color To be able to alter the background transparency of the species list on the main page, we need to have a rgb color. Indeed rgba() css function does not work on hexadecimal colors. It adds complexity but this seems to be the only way --- static/custom/custom.css.sample | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/static/custom/custom.css.sample b/static/custom/custom.css.sample index ed36e1f93..68c8ebf9c 100644 --- a/static/custom/custom.css.sample +++ b/static/custom/custom.css.sample @@ -3,5 +3,13 @@ :root { --main-color: #82c91e; + /* DO NOT USE rbg() BELOW + This variable is used for the tabEspece style + To set the opacity of the background color, + we need to use rgba() function to add opacity + to a rgb color. Unfortunately, this does not + work with hex values. So need to have rgb value... + */ + --main-color-rgb: 130, 201, 30; --second-color: #649b18; -} \ No newline at end of file +} From 0a644cf432f6582205f28d4db7ee43d2cf60b8f5 Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Thu, 29 Jul 2021 16:58:53 +0200 Subject: [PATCH 22/30] fix(species): Corrected a city bug in FicheEspece In FicheEspece: the displayed cities were not right. There was no instersection between the observation and the cities. Now for instance, for a department level observation all the cities in the department will appear... --- atlas/atlasRoutes.py | 5 ++++- .../repositories/vmCommunesRepository.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/atlas/atlasRoutes.py b/atlas/atlasRoutes.py index 34ba2a4d3..65c1bd36e 100644 --- a/atlas/atlasRoutes.py +++ b/atlas/atlasRoutes.py @@ -130,7 +130,10 @@ def ficheEspece(cd_ref): altitudes = vmAltitudesRepository.getAltitudesChilds(connection, cd_ref) months = vmMoisRepository.getMonthlyObservationsChilds(connection, cd_ref) synonyme = vmTaxrefRepository.getSynonymy(connection, cd_ref) - communes = vmCommunesRepository.getCommunesObservationsChilds(connection, cd_ref) + if current_app.config["AFFICHAGE_MAILLE"]: + communes = vmCommunesRepository.getCommunesObservationsChildsMailles(connection, cd_ref) + else: + communes = vmCommunesRepository.getCommunesObservationsChilds(connection, cd_ref) taxonomyHierarchy = vmTaxrefRepository.getAllTaxonomy(session, cd_ref) firstPhoto = vmMedias.getFirstPhoto( connection, cd_ref, current_app.config["ATTR_MAIN_PHOTO"] diff --git a/atlas/modeles/repositories/vmCommunesRepository.py b/atlas/modeles/repositories/vmCommunesRepository.py index 79292d017..e0d12a09b 100644 --- a/atlas/modeles/repositories/vmCommunesRepository.py +++ b/atlas/modeles/repositories/vmCommunesRepository.py @@ -78,3 +78,22 @@ def getCommunesObservationsChilds(connection, cd_ref): temp = {'insee': r.insee, 'commune_maj': r.commune_maj} listCommunes.append(temp) return listCommunes + +def getCommunesObservationsChildsMailles(connection, cd_ref): + sql = """ + SELECT DISTINCT (com.insee) as insee, com.commune_maj + FROM atlas.vm_communes com + JOIN atlas.vm_observations_mailles obs + ON st_intersects(obs.the_geom, com.the_geom) + WHERE obs.cd_ref in ( + SELECT * from atlas.find_all_taxons_childs(:thiscdref) + ) + OR obs.cd_ref = :thiscdref + ORDER BY com.commune_maj ASC + """ + req = connection.execute(text(sql), thiscdref=cd_ref) + listCommunes = list() + for r in req: + temp = {'insee': r.insee, 'commune_maj': r.commune_maj} + listCommunes.append(temp) + return listCommunes From 116379039b431768248b85137067f02e510f2271 Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Thu, 29 Jul 2021 18:15:47 +0200 Subject: [PATCH 23/30] feat(popup): Removes duplicates in popup In popup showing species, removes duplicates so that only different species are displayed --- static/mapGenerator.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/static/mapGenerator.js b/static/mapGenerator.js index 16fe8d7ce..f7d7d8f08 100644 --- a/static/mapGenerator.js +++ b/static/mapGenerator.js @@ -549,11 +549,13 @@ function printEspece(tabEspece, tabCdRef) { function onEachFeatureMailleLastObs(feature, layer) { + // "Set" removes the duplicates. We do not want the same + // species in the popup => Clearer popupContent = "Espèces observées dans la maille:
    " + - printEspece(feature.properties.list_taxon, feature.properties.list_cdref) + + printEspece([... new Set(feature.properties.list_taxon)], + [... new Set(feature.properties.list_cdref)]) + "
"; - layer.bindPopup(popupContent); filterMaille(feature, layer); From 358bd883986bcfb2d33b8b43bd34c0d260dea9dd Mon Sep 17 00:00:00 2001 From: Maxime Vergez Date: Tue, 3 Aug 2021 13:30:29 +0200 Subject: [PATCH 24/30] feat(sql): observations_maille.sql is more robust Changed the plain id number to a Select with a Like to enter directly the mesh cell type It might be a parameter in the settings.ini in the future --- data/observations_mailles.sql | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data/observations_mailles.sql b/data/observations_mailles.sql index d21777f35..430f4033c 100644 --- a/data/observations_mailles.sql +++ b/data/observations_mailles.sql @@ -16,7 +16,10 @@ CREATE MATERIALIZED VIEW atlas.vm_observations_mailles AS -- since s.the_geom_point can be either a point or a geometry -- corresponding to m.the_geom, we need to use a case -- Intersects the geom point with a 1km mesh cell if no sensibility - WHEN s.diffusion_level = 5 THEN st_intersects(s.the_geom_point, m.the_geom) AND m.id_type = 29 + WHEN s.diffusion_level = 5 THEN st_intersects(s.the_geom_point, m.the_geom) AND m.id_type = (( SELECT la.id_type + FROM ref_geo.l_areas la + WHERE la.area_code::text ~~ '1km%'::text + LIMIT 1)) -- We have no id from the syntheseff view so need to st_equals... ELSE st_equals(s.the_geom_point, m.the_geom) END From 44e7486e574f08cf36ea9703a51f52ec479cd958 Mon Sep 17 00:00:00 2001 From: TheoLechemia Date: Tue, 20 Sep 2022 17:11:17 +0200 Subject: [PATCH 25/30] calculate geom on sensitivity level --- data/gn2/atlas_ref_geo.sql | 80 +++++++++++++++------------------ data/gn2/atlas_synthese.sql | 20 ++++----- data/observations_mailles.sql | 2 +- data/update_vm_observations.sql | 2 +- install_db.sh | 8 ++-- 5 files changed, 53 insertions(+), 59 deletions(-) diff --git a/data/gn2/atlas_ref_geo.sql b/data/gn2/atlas_ref_geo.sql index f00436a0c..3084d5c71 100644 --- a/data/gn2/atlas_ref_geo.sql +++ b/data/gn2/atlas_ref_geo.sql @@ -2,6 +2,41 @@ --###Communes --################################ +DO $$ +BEGIN + DROP TABLE atlas.t_layer_territoire; +EXCEPTION WHEN others THEN + RAISE NOTICE 'view atlas.t_layer_territoire does not exist'; +END$$; + + +CREATE MATERIALIZED VIEW atlas.t_layer_territoire AS +WITH d AS ( + SELECT st_union(geom) , b.type_name + FROM ref_geo.l_areas l + JOIN ref_geo.bib_areas_types b USING(id_type) + WHERE REPLACE(b.type_code, ' ', '_') = :type_territoire + GROUP BY b.type_name +) +SELECT + 1::int as gid, + type_name as nom, + st_area(st_union)/10000 as surf_ha, + st_area(st_union)/1000000 as surf_km2, + ST_Perimeter(st_union)/1000 as perim_km, + st_transform(st_union, 3857) as the_geom +FROM d; + +CREATE INDEX index_gist_t_layer_territoire_the_geom + ON atlas.t_layer_territoire + USING gist + (the_geom); + +CREATE UNIQUE INDEX t_layer_territoire_gid_idx + ON atlas.t_layer_territoire + USING btree (gid); + + -- Suppression si temporaire des communes la table existe DO $$ BEGIN @@ -53,54 +88,13 @@ SELECT st_transform(c.geom, 3857)::geometry('MultiPolygon',3857) as the_geom, id_type FROM ref_geo.l_areas c -- See if st_intersects is the right function (st_within ?) - JOIN atlas.t_layer_territoire tlt ON st_intersects(tlt.the_geom, st_transform(c.geom, 3857)) + JOIN atlas.t_layer_territoire tlt ON st_intersects(tlt.the_geom, st_transform(c.geom, 3857)); CREATE UNIQUE INDEX t_mailles_territoire_id_maille_idx ON atlas.t_mailles_territoire USING btree (id_maille); ---################################ ---################################ ---###Territoires ---################################ ---################################ - -DO $$ -BEGIN - DROP TABLE atlas.t_layer_territoire; -EXCEPTION WHEN others THEN - RAISE NOTICE 'view atlas.t_layer_territoire does not exist'; -END$$; - - -CREATE MATERIALIZED VIEW atlas.t_layer_territoire AS -WITH d AS ( - SELECT st_union(geom) , b.type_name - FROM ref_geo.l_areas l - JOIN ref_geo.bib_areas_types b USING(id_type) - WHERE REPLACE(b.type_code, ' ', '_') = :type_territoire - GROUP BY b.type_name -) -SELECT - 1::int as gid, - type_name as nom, - st_area(st_union)/10000 as surf_ha, - st_area(st_union)/1000000 as surf_km2, - ST_Perimeter(st_union)/1000 as perim_km, - st_transform(st_union, 3857) as the_geom -FROM d; - -CREATE INDEX index_gist_t_layer_territoire_the_geom - ON atlas.t_layer_territoire - USING gist - (the_geom); - -CREATE UNIQUE INDEX t_layer_territoire_gid_idx - ON atlas.t_layer_territoire - USING btree (gid); - - -- Rafraichissement des vues contenant les données de l'atlas CREATE OR REPLACE FUNCTION atlas.refresh_materialized_view_ref_geo() RETURNS VOID AS $$ @@ -112,4 +106,4 @@ BEGIN REFRESH MATERIALIZED VIEW atlas.vm_communes; END -$$ LANGUAGE plpgsql; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/data/gn2/atlas_synthese.sql b/data/gn2/atlas_synthese.sql index b8e45c8ef..09b72ce6f 100644 --- a/data/gn2/atlas_synthese.sql +++ b/data/gn2/atlas_synthese.sql @@ -18,26 +18,26 @@ AS WITH areas AS ( s.observers AS observateurs, (s.altitude_min + s.altitude_max) / 2 AS altitude_retenue, CASE - WHEN dl.cd_nomenclature::text = '1'::text THEN ( SELECT a.geom + WHEN sensi.cd_nomenclature::text = '1'::text THEN ( SELECT a.geom FROM areas a - WHERE a.id_synthese = s.id_synthese AND a.type_code::text = 'COM'::text + WHERE a.id_synthese = s.id_synthese AND a.type_code::text = 'M1'::text LIMIT 1) - WHEN dl.cd_nomenclature::text = '2'::text THEN ( SELECT a.geom + WHEN sensi.cd_nomenclature::text = '2'::text THEN ( SELECT a.geom FROM areas a - WHERE a.id_synthese = s.id_synthese AND a.type_code::text = 'M10'::text + WHERE a.id_synthese = s.id_synthese AND a.type_code::text = 'M5'::text LIMIT 1) - WHEN dl.cd_nomenclature::text = '3'::text THEN ( SELECT a.geom + WHEN sensi.cd_nomenclature::text = '3'::text THEN ( SELECT a.geom FROM areas a - WHERE a.id_synthese = s.id_synthese AND a.type_code::text = 'DEP'::text + WHERE a.id_synthese = s.id_synthese AND a.type_code::text = 'M10'::text LIMIT 1) ELSE st_transform(s.the_geom_point, 3857) END AS the_geom_point, s.count_min AS effectif_total, - dl.cd_nomenclature::integer AS diffusion_level + sensi.cd_nomenclature::integer AS sensitivity FROM synthese.synthese s - LEFT JOIN synthese.t_nomenclatures dl ON s.id_nomenclature_diffusion_level = dl.id_nomenclature + LEFT JOIN synthese.t_nomenclatures sensi ON s.id_nomenclature_sensitivity = sensi.id_nomenclature LEFT JOIN synthese.t_nomenclatures st ON s.id_nomenclature_observation_status = st.id_nomenclature - WHERE (NOT dl.cd_nomenclature::text = '4'::text OR s.id_nomenclature_diffusion_level IS NULL) AND st.cd_nomenclature::text = 'Pr'::text + WHERE (NOT sensi.cd_nomenclature::text = '4'::text OR s.id_nomenclature_sensitivity IS NULL) AND st.cd_nomenclature::text = 'Pr'::text ) SELECT d.id_synthese, d.cd_nom, @@ -47,6 +47,6 @@ AS WITH areas AS ( d.the_geom_point, d.effectif_total, c.insee, - d.diffusion_level + d.sensitivity FROM obs_data d LEFT JOIN atlas.l_communes c ON st_within(d.the_geom_point, c.the_geom); diff --git a/data/observations_mailles.sql b/data/observations_mailles.sql index 430f4033c..938764101 100644 --- a/data/observations_mailles.sql +++ b/data/observations_mailles.sql @@ -16,7 +16,7 @@ CREATE MATERIALIZED VIEW atlas.vm_observations_mailles AS -- since s.the_geom_point can be either a point or a geometry -- corresponding to m.the_geom, we need to use a case -- Intersects the geom point with a 1km mesh cell if no sensibility - WHEN s.diffusion_level = 5 THEN st_intersects(s.the_geom_point, m.the_geom) AND m.id_type = (( SELECT la.id_type + WHEN s.sensitivity = 5 THEN st_intersects(s.the_geom_point, m.the_geom) AND m.id_type = (( SELECT la.id_type FROM ref_geo.l_areas la WHERE la.area_code::text ~~ '1km%'::text LIMIT 1)) diff --git a/data/update_vm_observations.sql b/data/update_vm_observations.sql index c339c2514..305e69acf 100644 --- a/data/update_vm_observations.sql +++ b/data/update_vm_observations.sql @@ -34,7 +34,7 @@ CREATE MATERIALIZED VIEW atlas.vm_observations AS s.effectif_total, tx.cd_ref, st_asgeojson(ST_Transform(ST_SetSrid(s.the_geom_point, 3857), 4326)) as geojson_point, - diffusion_level + sensitivity FROM synthese.syntheseff s LEFT JOIN atlas.vm_taxref tx ON tx.cd_nom = s.cd_nom JOIN atlas.t_layer_territoire m ON ST_Intersects(m.the_geom, s.the_geom_point); diff --git a/install_db.sh b/install_db.sh index ea7fdb84a..a90ae222e 100755 --- a/install_db.sh +++ b/install_db.sh @@ -316,13 +316,13 @@ then supprime boolean DEFAULT false, the_geom_point geometry('POINT',3857), effectif_total integer, - diffusion_level integer + sensitivity integer ); INSERT INTO synthese.syntheseff - (cd_nom, insee, observateurs, altitude_retenue, the_geom_point, effectif_total, diffusion_level) + (cd_nom, insee, observateurs, altitude_retenue, the_geom_point, effectif_total, sensitivity) VALUES (67111, 05122, 'Mon observateur', 1254, '0101000020110F0000B19F3DEA8636264124CB9EB2D66A5541', 3, 5); INSERT INTO synthese.syntheseff - (cd_nom, insee, observateurs, altitude_retenue, the_geom_point, effectif_total, diffusion_level) + (cd_nom, insee, observateurs, altitude_retenue, the_geom_point, effectif_total, sensitivity) VALUES (67111, 05122, 'Mon observateur 3', 940, '0101000020110F00001F548906D05E25413391E5EE2B795541', 2, 5);" &>> log/install_db.log sudo -n -u postgres -s psql -d $db_name -c "ALTER TABLE synthese.syntheseff OWNER TO "$owner_atlas";" fi @@ -359,7 +359,7 @@ then echo "Creation de la VM des observations de chaque taxon par mailles..." # Création de la vue matérialisée vm_mailles_observations (nombre d'observations par maille et par taxon) - sudo -n -u postgres -s psql -d $db_name -f data/observations_mailles.sql &>> log/install_db.log + export PGPASSWORD=$owner_atlas_pass;psql -d $db_name -U $owner_atlas -h $db_host -f data/observations_mailles.sql &>> log/install_db.log sudo -n -u postgres -s psql -d $db_name -c "ALTER TABLE atlas.vm_observations_mailles OWNER TO "$owner_atlas";" # Affectation de droits en lecture sur les VM à l'utilisateur de l'application ($user_pg) From 54ae9cc939787768984e424ea3236d0454a7cc93 Mon Sep 17 00:00:00 2001 From: TheoLechemia Date: Wed, 21 Sep 2022 15:35:14 +0200 Subject: [PATCH 26/30] fix db install + some front repercution --- atlas/modeles/entities/vmObservations.py | 2 +- .../vmObservationsMaillesRepository.py | 12 +- data/atlas.sql | 72 ++++-- data/gn2/atlas_ref_geo.sql | 21 -- data/gn2/atlas_synthese.sql | 64 +---- data/grant.sql | 1 - data/observations_mailles.sql | 39 ++- initAtlas.py | 2 +- static/mapGenerator.js | 225 +++++++++--------- 9 files changed, 203 insertions(+), 235 deletions(-) diff --git a/atlas/modeles/entities/vmObservations.py b/atlas/modeles/entities/vmObservations.py index 2151748ab..e5815cf49 100644 --- a/atlas/modeles/entities/vmObservations.py +++ b/atlas/modeles/entities/vmObservations.py @@ -48,7 +48,7 @@ class VmObservationsMailles(Base): metadata, Column("id_observation", Integer, primary_key=True, unique=True), Column("id_maille", Integer), - Column("id_type", Integer), + Column("type_code", Integer), Column("the_geom", Geometry), Column("geojson_maille", String(1000)), Column("annee", String(1000)), diff --git a/atlas/modeles/repositories/vmObservationsMaillesRepository.py b/atlas/modeles/repositories/vmObservationsMaillesRepository.py index 0a0533313..26b92c801 100644 --- a/atlas/modeles/repositories/vmObservationsMaillesRepository.py +++ b/atlas/modeles/repositories/vmObservationsMaillesRepository.py @@ -17,10 +17,10 @@ def getObservationsMaillesChilds(session, cd_ref, year_min=None, year_max=None): func.count(VmObservationsMailles.id_observation).label("nb_obs"), func.max(VmObservationsMailles.annee).label("last_observation"), VmObservationsMailles.id_maille, - VmObservationsMailles.id_type, + VmObservationsMailles.type_code, VmObservationsMailles.geojson_maille ) - .group_by(VmObservationsMailles.id_maille, VmObservationsMailles.geojson_maille, VmObservationsMailles.id_type) + .group_by(VmObservationsMailles.id_maille, VmObservationsMailles.geojson_maille, VmObservationsMailles.type_code) .filter( or_( VmObservationsMailles.cd_ref.in_(subquery), @@ -38,7 +38,7 @@ def getObservationsMaillesChilds(session, cd_ref, year_min=None, year_max=None): geometry=json.loads(o.geojson_maille), properties={ "id_maille": o.id_maille, - "id_type": o.id_type, + "type_code": o.type_code, "nb_observations": o.nb_obs, "last_observation": o.last_observation, }, @@ -75,7 +75,7 @@ def lastObservationsMailles(connection, mylimit, idPhoto): temp = { "id_observation": o.id_observation, "id_maille": o.id_maille, - "id_type": o.id_type, + "type_code": o.type_code, "cd_ref": o.cd_ref, "dateobs": str(o.dateobs), "altitude_retenue": o.altitude_retenue, @@ -95,7 +95,7 @@ def lastObservationsCommuneMaille(connection, mylimit, insee): obs.cd_ref, obs.dateobs, t.lb_nom, t.nom_vern, obs.the_geom as l_geom, obs.geojson_maille, obs.id_maille, - obs.id_type + obs.type_code FROM atlas.vm_observations_mailles obs JOIN atlas.vm_communes c ON ST_Intersects(obs.the_geom, c.the_geom) @@ -118,7 +118,7 @@ def lastObservationsCommuneMaille(connection, mylimit, insee): "taxon": taxon, "geojson_maille": json.loads(o.geojson_maille), "id_maille": o.id_maille, - "id_type": o.id_type, + "type_code": o.type_code, } obsList.append(temp) return obsList diff --git a/data/atlas.sql b/data/atlas.sql index 669251b90..0b779580d 100644 --- a/data/atlas.sql +++ b/data/atlas.sql @@ -14,23 +14,67 @@ CREATE INDEX ON atlas.vm_taxref (nom_complet); CREATE INDEX ON atlas.vm_taxref (nom_valide); +CREATE MATERIALIZED VIEW atlas.vm_cor_area_synthese +TABLESPACE pg_default +AS SELECT sa.id_synthese, + sa.id_area, + a.centroid, + st_transform(a.geom, 4326) AS geom, + st_asgeojson(st_transform(a.geom, 4326)) AS geojson_4326, + st_transform(a.centroid, 4326) AS centroid_4326, + t.type_code, + sensi.cd_nomenclature, + CASE + WHEN sensi.cd_nomenclature::text = '1'::text AND t.type_code::text = 'M1'::text THEN true + WHEN sensi.cd_nomenclature::text = '2'::text AND t.type_code::text = 'M5'::text THEN true + WHEN sensi.cd_nomenclature::text = '3'::text AND t.type_code::text = 'M10'::text THEN true + WHEN (sensi.cd_nomenclature::text = '0'::TEXT OR sensi.cd_nomenclature::text IS NULL) AND t.type_code::text = 'M5'::text THEN true + ELSE false + END AS is_blurred_geom + FROM synthese.synthese s + JOIN synthese.cor_area_synthese sa ON sa.id_synthese = s.id_synthese + JOIN ref_geo.l_areas a ON sa.id_area = a.id_area + JOIN ref_geo.bib_areas_types t ON a.id_type = t.id_type + LEFT JOIN synthese.t_nomenclatures sensi ON s.id_nomenclature_sensitivity = sensi.id_nomenclature + WHERE (t.type_code::text = ANY (ARRAY['M1'::character varying, 'M5'::character varying, 'M10'::character varying]::text[])) + AND (NOT sensi.cd_nomenclature::text = '4'::TEXT OR sensi.cd_nomenclature IS NULL ) +WITH DATA; --Toutes les observations --DROP materialized view atlas.vm_observations; -CREATE MATERIALIZED VIEW atlas.vm_observations AS - SELECT s.id_synthese AS id_observation, - s.insee, - s.dateobs, - s.observateurs, - s.altitude_retenue, - st_centroid(s.the_geom_point) as the_geom_point, - s.effectif_total, - tx.cd_ref, - st_asgeojson(ST_Transform(ST_SetSrid(st_centroid(s.the_geom_point), 3857), 4326)) as geojson_point, - s.diffusion_level - FROM synthese.syntheseff s - LEFT JOIN atlas.vm_taxref tx ON tx.cd_nom = s.cd_nom - JOIN atlas.t_layer_territoire m ON ST_Intersects(s.the_geom_point, m.the_geom); + + CREATE MATERIALIZED VIEW atlas.vm_observations AS + WITH centroid AS ( + SELECT st_centroid(st_union(cor.geom)) AS geom_point, s.id_synthese + FROM synthese.synthese s + JOIN atlas.vm_cor_area_synthese cor ON cor.id_synthese = s.id_synthese + WHERE cor.id_synthese = s.id_synthese AND cor.is_blurred_geom IS TRUE + GROUP BY s.id_synthese + ) + SELECT s.id_synthese AS id_observation, + com.insee , + s.date_min AS dateobs, + (s.altitude_min + s.altitude_max) / 2 AS altitude_retenue, + s.observers AS observateurs, + tx.cd_ref, + s.id_dataset, + c.geom_point, + CASE + WHEN sensi.cd_nomenclature = '0' THEN st_transform(s.the_geom_point, 3857) + ELSE st_transform(c.geom_point, 3857) + END AS the_geom_point, + CASE + WHEN sensi.cd_nomenclature = '0' THEN st_asgeojson(st_transform(s.the_geom_point, 4326)) + ELSE st_asgeojson(st_transform(c.geom_point, 4326)) + END AS geojson_point, + sensi.cd_nomenclature AS cd_sensitivity + FROM synthese.synthese s + JOIN atlas.vm_taxref tx ON tx.cd_nom = s.cd_nom + LEFT JOIN synthese.t_nomenclatures sensi ON s.id_nomenclature_sensitivity = sensi.id_nomenclature + JOIN centroid c ON c.id_synthese = s.id_synthese + JOIN atlas.l_communes com ON st_intersects(st_transform(s.the_geom_point, 3857), com.the_geom) + ; + CREATE UNIQUE INDEX ON atlas.vm_observations (id_observation); CREATE INDEX ON atlas.vm_observations (cd_ref); diff --git a/data/gn2/atlas_ref_geo.sql b/data/gn2/atlas_ref_geo.sql index 3084d5c71..b941612f2 100644 --- a/data/gn2/atlas_ref_geo.sql +++ b/data/gn2/atlas_ref_geo.sql @@ -74,26 +74,6 @@ CREATE UNIQUE INDEX l_communes_insee_idx --################################ --################################ -DO $$ -BEGIN - DROP TABLE atlas.t_mailles_territoire; -EXCEPTION WHEN others THEN - RAISE NOTICE 'view atlas.t_mailles_territoire does not exist'; -END$$; - -CREATE MATERIALIZED VIEW atlas.t_mailles_territoire AS -SELECT st_transform(c.geom, 3857)::geometry('MultiPolygon',3857) as the_geom, - st_asgeojson(st_transform(c.geom, 4326)) AS geojson_maille, - id_area as id_maille, - id_type -FROM ref_geo.l_areas c - -- See if st_intersects is the right function (st_within ?) - JOIN atlas.t_layer_territoire tlt ON st_intersects(tlt.the_geom, st_transform(c.geom, 3857)); - -CREATE UNIQUE INDEX t_mailles_territoire_id_maille_idx - ON atlas.t_mailles_territoire - USING btree (id_maille); - -- Rafraichissement des vues contenant les données de l'atlas CREATE OR REPLACE FUNCTION atlas.refresh_materialized_view_ref_geo() @@ -101,7 +81,6 @@ RETURNS VOID AS $$ BEGIN REFRESH MATERIALIZED VIEW atlas.t_layer_territoire; - REFRESH MATERIALIZED VIEW atlas.t_mailles_territoire; REFRESH MATERIALIZED VIEW atlas.l_communes; REFRESH MATERIALIZED VIEW atlas.vm_communes; diff --git a/data/gn2/atlas_synthese.sql b/data/gn2/atlas_synthese.sql index 09b72ce6f..688c276f1 100644 --- a/data/gn2/atlas_synthese.sql +++ b/data/gn2/atlas_synthese.sql @@ -1,52 +1,14 @@ -- Creation d'une vue permettant de reproduire le contenu de la table du même nom dans les versions précédentes -CREATE OR REPLACE VIEW synthese.syntheseff -AS WITH areas AS ( - SELECT DISTINCT ON (sa.id_synthese, a.id_area) sa.id_synthese, - sa.id_area, - a.centroid, - st_transform(a.geom, 3857) AS geom, - st_transform(a.centroid, 3857) AS centroid_3857, - t.type_code - FROM synthese.cor_area_synthese sa - JOIN ref_geo.l_areas a ON sa.id_area = a.id_area - JOIN ref_geo.bib_areas_types t ON a.id_type = t.id_type - WHERE t.type_code::text = ANY (ARRAY['M10'::character varying::text, 'M1'::character varying::text, 'COM'::character varying::text, 'DEP'::character varying::text]) - ), obs_data AS ( - SELECT s.id_synthese, - s.cd_nom, - s.date_min AS dateobs, - s.observers AS observateurs, - (s.altitude_min + s.altitude_max) / 2 AS altitude_retenue, - CASE - WHEN sensi.cd_nomenclature::text = '1'::text THEN ( SELECT a.geom - FROM areas a - WHERE a.id_synthese = s.id_synthese AND a.type_code::text = 'M1'::text - LIMIT 1) - WHEN sensi.cd_nomenclature::text = '2'::text THEN ( SELECT a.geom - FROM areas a - WHERE a.id_synthese = s.id_synthese AND a.type_code::text = 'M5'::text - LIMIT 1) - WHEN sensi.cd_nomenclature::text = '3'::text THEN ( SELECT a.geom - FROM areas a - WHERE a.id_synthese = s.id_synthese AND a.type_code::text = 'M10'::text - LIMIT 1) - ELSE st_transform(s.the_geom_point, 3857) - END AS the_geom_point, - s.count_min AS effectif_total, - sensi.cd_nomenclature::integer AS sensitivity - FROM synthese.synthese s - LEFT JOIN synthese.t_nomenclatures sensi ON s.id_nomenclature_sensitivity = sensi.id_nomenclature - LEFT JOIN synthese.t_nomenclatures st ON s.id_nomenclature_observation_status = st.id_nomenclature - WHERE (NOT sensi.cd_nomenclature::text = '4'::text OR s.id_nomenclature_sensitivity IS NULL) AND st.cd_nomenclature::text = 'Pr'::text - ) - SELECT d.id_synthese, - d.cd_nom, - d.dateobs, - d.observateurs, - d.altitude_retenue, - d.the_geom_point, - d.effectif_total, - c.insee, - d.sensitivity - FROM obs_data d - LEFT JOIN atlas.l_communes c ON st_within(d.the_geom_point, c.the_geom); +-- CREATE OR REPLACE VIEW synthese.syntheseff +-- SELECT d.id_synthese, +-- s.cd_nom, +-- s.date_min AS dateobs, +-- s.observers AS observateurs, +-- (s.altitude_min + s.altitude_max) / 2 AS altitude_retenue, +-- s.the_geom_point, +-- s.count_min AS effectif_total, +-- sensi.cd_nomenclature::integer AS sensitivity +-- c.insee +-- FROM synthese.synthese s +-- LEFT JOIN atlas.l_communes c ON st_within(d.the_geom_point, c.the_geom) +-- ; diff --git a/data/grant.sql b/data/grant.sql index 00e13c230..8e8b16890 100644 --- a/data/grant.sql +++ b/data/grant.sql @@ -36,4 +36,3 @@ GRANT SELECT ON TABLE atlas.vm_altitudes TO my_reader_user; GRANT SELECT ON TABLE atlas.bib_altitudes TO my_reader_user; GRANT EXECUTE ON FUNCTION atlas.find_all_taxons_childs(integer) TO my_reader_user; GRANT SELECT ON TABLE atlas.bib_taxref_rangs TO my_reader_user; -GRANT SELECT ON TABLE atlas.t_mailles_territoire TO my_reader_user; diff --git a/data/observations_mailles.sql b/data/observations_mailles.sql index 938764101..ceb68972f 100644 --- a/data/observations_mailles.sql +++ b/data/observations_mailles.sql @@ -1,31 +1,20 @@ -- Creation de la VM des observations de chaque taxon par mailles... -CREATE MATERIALIZED VIEW atlas.vm_observations_mailles AS - SELECT tx.cd_ref, - s.id_synthese AS id_observation, - s.dateobs, - m.id_maille, - m.id_type, - m.the_geom, - m.geojson_maille, - date_part('year'::text, s.dateobs) AS annee - FROM synthese.syntheseff s - LEFT JOIN atlas.vm_taxref tx ON tx.cd_nom = s.cd_nom - JOIN atlas.t_mailles_territoire m ON - CASE - -- since s.the_geom_point can be either a point or a geometry - -- corresponding to m.the_geom, we need to use a case - -- Intersects the geom point with a 1km mesh cell if no sensibility - WHEN s.sensitivity = 5 THEN st_intersects(s.the_geom_point, m.the_geom) AND m.id_type = (( SELECT la.id_type - FROM ref_geo.l_areas la - WHERE la.area_code::text ~~ '1km%'::text - LIMIT 1)) - -- We have no id from the syntheseff view so need to st_equals... - ELSE st_equals(s.the_geom_point, m.the_geom) - END -WITH DATA; +CREATE MATERIALIZED VIEW atlas.vm_observations_mailles AS + SELECT + obs.id_observation, + obs.cd_ref, + cor.id_area as id_maille, + cor.geojson_4326 as geojson_maille, + st_transform(cor.geom, 3857) as the_geom, + cor.type_code as type_code, +date_part('year', dateobs) as annee, +obs.dateobs +FROM atlas.vm_observations obs +JOIN atlas.vm_cor_area_synthese cor ON cor.id_synthese = obs.id_observation AND cor.is_blurred_geom IS TRUE +; + -create unique index on atlas.vm_observations_mailles (id_observation); create index on atlas.vm_observations_mailles (id_maille); create index on atlas.vm_observations_mailles (cd_ref); -- create index on atlas.vm_observations_mailles (geojson_maille); diff --git a/initAtlas.py b/initAtlas.py index 09bc94ed2..d6d1485ad 100644 --- a/initAtlas.py +++ b/initAtlas.py @@ -1,6 +1,6 @@ import os import sys -from flask import Flask, render_template +from flask import Flask from flask_sqlalchemy import SQLAlchemy from werkzeug.serving import run_simple diff --git a/static/mapGenerator.js b/static/mapGenerator.js index f7d7d8f08..556fe563c 100644 --- a/static/mapGenerator.js +++ b/static/mapGenerator.js @@ -1,10 +1,10 @@ function generateMap() { // Map initialization firstMapTile = L.tileLayer(configuration.MAP.FIRST_MAP.url, { - attribution: configuration.MAP.FIRST_MAP.attribution + attribution: configuration.MAP.FIRST_MAP.attribution, }); orthoMap = L.tileLayer(configuration.MAP.SECOND_MAP.url, { - attribution: configuration.MAP.SECOND_MAP.attribution + attribution: configuration.MAP.SECOND_MAP.attribution, }); baseMap = {}; @@ -18,48 +18,50 @@ function generateMap() { geosearch: true, zoom: configuration.MAP.ZOOM, layers: [firstMapTile], - fullscreenControl: true + fullscreenControl: true, }); - department = L.featureGroup(); - cities = L.featureGroup(); - tenCell = L.featureGroup(); - oneCell = L.featureGroup(); + // department = L.featureGroup(); + // cities = L.featureGroup(); + // tenCell = L.featureGroup(); + + m10FeatureGroup = L.featureGroup(); + m5FeatureGroup = L.featureGroup(); + m1FeatureGroup = L.featureGroup(); var overlays = { - "Departement": department, - "Communes": cities, - "10km²": tenCell, - "1km²": oneCell - } + "Maille 10": m10FeatureGroup, + "Maille 5": m5FeatureGroup, + "Maille 1": m1FeatureGroup, + }; // Add layers - control = L.control.layers(null, overlays) - + control = L.control.layers(null, overlays); + if (configuration.AFFICHAGE_MAILLE) { - control.addTo(map) + control.addTo(map); } // Activate layers - Object.values(overlays).forEach(e => map.addLayer(e)) + Object.values(overlays).forEach((e) => map.addLayer(e)); // Keep Layers in the same order as specified by the // overlays variable so Departement under Commune // under 10km2 under 1km2 - map.on('overlayadd', function(e){ - Object.values(overlays).forEach(e => e.bringToFront()) - }) + map.on("overlayadd", function (e) { + Object.values(overlays).forEach((e) => e.bringToFront()); + }); // Style of territory on map territoryStyle = { fill: false, color: configuration.MAP.BORDERS_COLOR, - weight: configuration.MAP.BORDERS_WEIGHT + weight: configuration.MAP.BORDERS_WEIGHT, }; // Add limits of the territory to the map - $(document).ready(function() { - $.getJSON(url_limit_territory, function(json) { + $(document).ready(function () { + $.getJSON(url_limit_territory, function (json) { L.geoJson(json, { - style: territoryStyle + style: territoryStyle, }).addTo(map); }); }); @@ -68,10 +70,10 @@ function generateMap() { var LayerControl = L.Control.extend({ options: { - position: "bottomleft" + position: "bottomleft", }, - onAdd: function(map) { + onAdd: function (map) { currentTileMap = "topo"; var container = L.DomUtil.create( "div", @@ -91,7 +93,7 @@ function generateMap() { $(container).attr("data-toggle", "tooltip"); $(container).attr("data-original-title", "Photos aérienne"); - container.onclick = function() { + container.onclick = function () { if (currentTileMap == "topo") { container.style.backgroundImage = "url(" + @@ -113,7 +115,7 @@ function generateMap() { } }; return container; - } + }, }); map.addControl(new LayerControl()); @@ -126,10 +128,7 @@ function generateMap() { fullScreenButton.attr("data-original-title", "Plein écran"); $(".leaflet-control-fullscreen-button").removeAttr("title"); - return map; - - } //****** Fonction fiche espècce *********** @@ -158,7 +157,6 @@ function onEachFeaturePoint(feature, layer) { // popup Maille function onEachFeatureMaille(feature, layer) { - popupContent = "Nombre d'observation(s): " + feature.properties.nb_observations + @@ -166,7 +164,7 @@ function onEachFeatureMaille(feature, layer) { feature.properties.last_observation + " "; layer.bindPopup(popupContent); - + filterMaille(feature, layer); zoomMaille(layer); @@ -196,24 +194,24 @@ function styleMaille(feature) { fillColor: getColor(feature.properties.nb_observations), weight: 2, color: "#333333", - fillOpacity: 0.8 + fillOpacity: 0.8, }; } function zoomMaille(layer) { - layer.on('click', function(e){ + layer.on("click", function (e) { bounds = e.sourceTarget.feature.geometry.coordinates; - bounds = bounds.map(b => { - return b.map(c => { - return c.map(d => [d[1], d[0]]) - }) - }) + bounds = bounds.map((b) => { + return b.map((c) => { + return c.map((d) => [d[1], d[0]]); + }); + }); map.fitBounds(bounds); }); } function generateLegendMaille() { - legend.onAdd = function(map) { + legend.onAdd = function (map) { var div = L.DomUtil.create("div", "info legend"), grades = [0, 1, 2, 5, 10, 20, 50, 100], labels = [" Nombre
d'observations

"]; @@ -249,7 +247,7 @@ function generateGeojsonMaille(observations, yearMin, yearMax) { id_maille: idMaille, nb_observations: 1, last_observation: observations[i].annee, - tabDateobs: [new Date(observations[i].dateobs)] + tabDateobs: [new Date(observations[i].dateobs)], }; var j = i + 1; while (j < observations.length && observations[j].id_maille <= idMaille) { @@ -268,7 +266,7 @@ function generateGeojsonMaille(observations, yearMin, yearMax) { myGeoJson.features.push({ type: "Feature", properties: properties, - geometry: geometry + geometry: geometry, }); // on avance jusqu' à j i = j; @@ -284,10 +282,10 @@ function generateGeojsonMaille(observations, yearMin, yearMax) { function displayMailleLayerFicheEspece(observationsMaille) { myGeoJson = observationsMaille; - + currentLayer = L.geoJson(myGeoJson, { onEachFeature: onEachFeatureMaille, - style: styleMaille + style: styleMaille, }); currentLayer.addTo(map); @@ -302,12 +300,12 @@ function generateGeojsonMailleCommune(observations) { while (i < observations.length) { geometry = observations[i].geojson_maille; idMaille = observations[i].id_maille; - idType = observations[i].id_type; + typeCode = observations[i].type_code; properties = { - id_type: idType, + type_code: typeCode, id_maille: idMaille, nb_observations: 1, - last_observation: observations[i].annee + last_observation: observations[i].annee, }; var j = i + 1; while (j < observations.length && observations[j].id_maille <= idMaille) { @@ -321,7 +319,7 @@ function generateGeojsonMailleCommune(observations) { myGeoJson.features.push({ type: "Feature", properties: properties, - geometry: geometry + geometry: geometry, }); // on avance jusqu' à j i = j; @@ -334,7 +332,7 @@ function displayMailleLayerCommune(observations) { myGeoJson = generateGeojsonMailleCommune(observations); currentLayer = L.geoJson(myGeoJson, { onEachFeature: onEachFeatureMaille, - style: styleMaille + style: styleMaille, }); currentLayer.addTo(map); @@ -352,7 +350,9 @@ function generateGeojsonPointFicheEspece( var filteredGeoJsonPoint = Object.assign({}, geojsonPoint); // si on a touché le slider on filtre sinon on retourne directement le geojson if (yearMin && yearMax && sliderTouch) { - filteredGeoJsonPoint.features = geojsonPoint.features.filter(function(obs) { + filteredGeoJsonPoint.features = geojsonPoint.features.filter(function ( + obs + ) { return obs.properties.year >= yearMin && obs.properties.year <= yearMax; }); return filteredGeoJsonPoint; @@ -381,16 +381,16 @@ function displayMarkerLayerFicheEspece( ); if (typeof pointDisplayOptionsFicheEspece == "undefined") { - pointDisplayOptionsFicheEspece = function(feature) { + pointDisplayOptionsFicheEspece = function (feature) { return {}; }; } currentLayer = L.geoJson(myGeoJson, { onEachFeature: onEachFeaturePoint, - pointToLayer: function(feature, latlng) { + pointToLayer: function (feature, latlng) { return L.circleMarker(latlng, pointDisplayOptionsFicheEspece(feature)); - } + }, }); if (myGeoJson.features.length > configuration.LIMIT_CLUSTER_POINT) { newLayer = currentLayer; @@ -402,7 +402,7 @@ function displayMarkerLayerFicheEspece( } if (typeof divLegendeFicheEspece !== "undefined") { - legend.onAdd = function(map) { + legend.onAdd = function (map) { var div = L.DomUtil.create("div", "info legend"); div.innerHTML = divLegendeFicheEspece; return div; @@ -416,7 +416,6 @@ function displayMarkerLayerFicheEspece( /* *** Point ****/ function onEachFeaturePointLastObs(feature, layer) { - popupContent = "Espèce: " + feature.properties.taxon + @@ -459,7 +458,7 @@ function onEachFeaturePointCommune(feature, layer) { function generateGeojsonPointLastObs(observationsPoint) { myGeoJson = { type: "FeatureCollection", features: [] }; - observationsPoint.forEach(function(obs) { + observationsPoint.forEach(function (obs) { properties = obs; properties["dateobsCompare"] = new Date(obs.dateobs); properties["dateobs"] = obs.dateobs; @@ -467,7 +466,7 @@ function generateGeojsonPointLastObs(observationsPoint) { myGeoJson.features.push({ type: "Feature", properties: properties, - geometry: obs.geojson_point + geometry: obs.geojson_point, }); }); return myGeoJson; @@ -476,23 +475,23 @@ function generateGeojsonPointLastObs(observationsPoint) { function displayMarkerLayerPointLastObs(observationsPoint) { myGeoJson = generateGeojsonPointLastObs(observationsPoint); if (typeof pointDisplayOptionsFicheCommuneHome == "undefined") { - pointDisplayOptionsFicheCommuneHome = function(feature) { + pointDisplayOptionsFicheCommuneHome = function (feature) { return {}; }; } currentLayer = L.geoJson(myGeoJson, { onEachFeature: onEachFeaturePointLastObs, - pointToLayer: function(feature, latlng) { + pointToLayer: function (feature, latlng) { return L.circleMarker( latlng, pointDisplayOptionsFicheCommuneHome(feature) ); - } + }, }); map.addLayer(currentLayer); if (typeof divLegendeFicheCommuneHome !== "undefined") { - legend.onAdd = function(map) { + legend.onAdd = function (map) { var div = L.DomUtil.create("div", "info legend"); div.innerHTML = divLegendeFicheCommuneHome; return div; @@ -504,19 +503,19 @@ function displayMarkerLayerPointLastObs(observationsPoint) { function displayMarkerLayerPointCommune(observationsPoint) { myGeoJson = generateGeojsonPointLastObs(observationsPoint); if (typeof pointDisplayOptionsFicheCommuneHome == "undefined") { - pointDisplayOptionsFicheCommuneHome = function(feature) { + pointDisplayOptionsFicheCommuneHome = function (feature) { return {}; }; } currentLayer = L.geoJson(myGeoJson, { onEachFeature: onEachFeaturePointCommune, - pointToLayer: function(feature, latlng) { + pointToLayer: function (feature, latlng) { return L.circleMarker( latlng, pointDisplayOptionsFicheCommuneHome(feature) ); - } + }, }); newLayer = currentLayer; @@ -540,7 +539,11 @@ function printEspece(tabEspece, tabCdRef) { stringEspece += "
  • " + tabEspece[i] + "
  • "; + "/espece/" + + tabCdRef[i] + + "'>" + + tabEspece[i] + + ""; i = i + 1; } @@ -548,13 +551,14 @@ function printEspece(tabEspece, tabCdRef) { } function onEachFeatureMailleLastObs(feature, layer) { - // "Set" removes the duplicates. We do not want the same // species in the popup => Clearer popupContent = "Espèces observées dans la maille:
      " + - printEspece([... new Set(feature.properties.list_taxon)], - [... new Set(feature.properties.list_cdref)]) + + printEspece( + [...new Set(feature.properties.list_taxon)], + [...new Set(feature.properties.list_cdref)] + ) + "
    "; layer.bindPopup(popupContent); @@ -564,17 +568,17 @@ function onEachFeatureMailleLastObs(feature, layer) { var selected = false; - layer.on('click', function (layer) { + layer.on("click", function (layer) { resetStyleMailles(); this.setStyle(styleMailleClickedOrHover(layer.target)); selected = true; }); - layer.on('mouseover', function (layer) { + layer.on("mouseover", function (layer) { this.setStyle(styleMailleClickedOrHover(layer.target)); selected = false; }); - layer.on('mouseout', function () { + layer.on("mouseout", function () { if (!selected) { this.setStyle(styleMailleLastObs()); } @@ -586,61 +590,52 @@ function styleMailleLastObs() { opacity: 1, weight: 2, color: "#333333", - fillOpacity: 0 + fillOpacity: 0, }; } function styleMailleClickedOrHover(layer) { - var id = layer.feature.properties.id_type; - var fillColor = getComputedStyle(document.body).getPropertyValue('--main-color'); + var mailleCode = layer.feature.properties.type_code; + var fillColor = getComputedStyle(document.body).getPropertyValue( + "--main-color" + ); var fillOpacity = 0.5; - if ( id === 26) { + if (mailleCode === "M1") { var fillOpacity = 0.2; - } - else if (id === 25 ) { + } else if (mailleCode === "M5") { var fillOpacity = 0.4; - } - else if ( id === 27) { + } else if (mailleCode === "M10") { var fillOpacity = 0.6; - } - else { + } else { var fillOpacity = 0.85; } var options = layer.options; return { ...options, fillColor: fillColor, - fillOpacity: fillOpacity - } + fillOpacity: fillOpacity, + }; } function resetStyleMailles() { // set style for all cells - map.eachLayer(function(layer){ + map.eachLayer(function (layer) { if (layer.feature && layer.feature.properties.id_type) { - layer.setStyle(styleMailleLastObs()) - }; + layer.setStyle(styleMailleLastObs()); + } }); } function filterMaille(feature, layer) { - id = feature.properties.id_type; - if ( id === 26) { - department.addLayer(layer); - // Need to do it there otherwise it will be - // in front of cities featureGroup - department.bringToBack(); - } - else if (id === 25 ) { - cities.addLayer(layer); - } - else if ( id === 27) { - tenCell.addLayer(layer); - } - else { - oneCell.addLayer(layer); - oneCell.bringToFront(); + mailleTypeCode = feature.properties.type_code; + if (mailleTypeCode === "M10") { + m10FeatureGroup.addLayer(layer); + m10FeatureGroup.bringToBack(); + } else if (mailleTypeCode === "M5") { + m5FeatureGroup.addLayer(layer); + } else if (mailleTypeCode === "M1") { + m1FeatureGroup.addLayer(layer); } } @@ -655,7 +650,7 @@ function generateGeoJsonMailleLastObs(observations) { id_maille: idMaille, list_taxon: [observations[i].taxon], list_cdref: [observations[i].cd_ref], - list_id_observation: [observations[i].id_observation] + list_id_observation: [observations[i].id_observation], }; var j = i + 1; while (j < observations.length && observations[j].id_maille == idMaille) { @@ -667,7 +662,7 @@ function generateGeoJsonMailleLastObs(observations) { myGeoJson.features.push({ type: "Feature", properties: properties, - geometry: geometry + geometry: geometry, }); // on avance jusqu' à j i = j; @@ -689,10 +684,10 @@ function displayMailleLayerLastObs(observations) { var geojsonMaille = generateGeoJsonMailleLastObs(observations); currentLayer = L.geoJson(geojsonMaille, { onEachFeature: onEachFeatureMailleLastObs, - style: styleMailleLastObs + style: styleMailleLastObs, }); // Very important otherwise currentLayer cannot be removed by - // mapCommune.js + // mapCommune.js currentLayer.addTo(map); } @@ -707,11 +702,11 @@ function generateLegende(htmlLegend) { var legendControl = L.Control.extend({ options: { - position: "topleft" + position: "topleft", //control position - allowed: 'topleft', 'topright', 'bottomleft', 'bottomright' }, - onAdd: function(map) { + onAdd: function (map) { var container = L.DomUtil.create( "div", "leaflet-bar leaflet-control leaflet-control-custom" @@ -731,11 +726,11 @@ function generateLegende(htmlLegend) { $(container).attr("data-toggle", "tooltip"); $(container).attr("data-original-title", "Légende"); - container.onclick = function() { + container.onclick = function () { if (legendActiv == false) { legend = L.control({ position: "topleft" }); - legend.onAdd = function(map) { + legend.onAdd = function (map) { (div = L.DomUtil.create("div", "info legend")), $(div).addClass("generalLegend"); @@ -751,7 +746,7 @@ function generateLegende(htmlLegend) { } }; return container; - } + }, }); map.addControl(new legendControl()); @@ -761,11 +756,11 @@ var mySlider; function generateSliderOnMap() { var SliderControl = L.Control.extend({ options: { - position: "bottomleft" + position: "bottomleft", //control position - allowed: 'topleft', 'topright', 'bottomleft', 'bottomright' }, - onAdd: function(map) { + onAdd: function (map) { var sliderContainer = L.DomUtil.create( "div", "leaflet-bar leaflet-control leaflet-slider-control" @@ -787,7 +782,7 @@ function generateSliderOnMap() { ); L.DomEvent.disableClickPropagation(sliderContainer); return sliderContainer; - } + }, }); map.addControl(new SliderControl()); @@ -796,7 +791,7 @@ function generateSliderOnMap() { value: [taxonYearMin, YEARMAX], min: taxonYearMin, max: YEARMAX, - step: configuration.MAP.STEP + step: configuration.MAP.STEP, }); $("#yearMax").html("    " + YEARMAX); From 1d110c05a881bd36f0875f229571696841b04e7a Mon Sep 17 00:00:00 2001 From: TheoLechemia Date: Thu, 22 Sep 2022 10:13:03 +0200 Subject: [PATCH 27/30] set defaut maille in parameters --- data/atlas.sql | 2 +- install_db.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/atlas.sql b/data/atlas.sql index 0b779580d..856d02564 100644 --- a/data/atlas.sql +++ b/data/atlas.sql @@ -28,7 +28,7 @@ AS SELECT sa.id_synthese, WHEN sensi.cd_nomenclature::text = '1'::text AND t.type_code::text = 'M1'::text THEN true WHEN sensi.cd_nomenclature::text = '2'::text AND t.type_code::text = 'M5'::text THEN true WHEN sensi.cd_nomenclature::text = '3'::text AND t.type_code::text = 'M10'::text THEN true - WHEN (sensi.cd_nomenclature::text = '0'::TEXT OR sensi.cd_nomenclature::text IS NULL) AND t.type_code::text = 'M5'::text THEN true + WHEN (sensi.cd_nomenclature::text = '0'::TEXT OR sensi.cd_nomenclature::text IS NULL) AND t.type_code::text = :default_maille::text THEN true ELSE false END AS is_blurred_geom FROM synthese.synthese s diff --git a/install_db.sh b/install_db.sh index a90ae222e..0d0604d5f 100755 --- a/install_db.sh +++ b/install_db.sh @@ -350,7 +350,7 @@ then sudo sed -i "s/INSERT_ALTITUDE/${insert}/" /tmp/atlas.sql - export PGPASSWORD=$owner_atlas_pass;psql -d $db_name -U $owner_atlas -h $db_host -f /tmp/atlas.sql &>> log/install_db.log + export PGPASSWORD=$owner_atlas_pass;psql -d $db_name -U $owner_atlas -h $db_host -v default_maille=$type_maille -f /tmp/atlas.sql &>> log/install_db.log sudo -n -u postgres -s psql -d $db_name -c "ALTER TABLE atlas.bib_altitudes OWNER TO "$owner_atlas";" sudo -n -u postgres -s psql -d $db_name -c "ALTER TABLE atlas.bib_taxref_rangs OWNER TO "$owner_atlas";" sudo -n -u postgres -s psql -d $db_name -c "ALTER TABLE atlas.bib_taxref_rangs OWNER TO "$owner_atlas";" From a6bef1c1410dce5ceaeb757673d170a902f2f592 Mon Sep 17 00:00:00 2001 From: TheoLechemia Date: Thu, 22 Sep 2022 11:34:50 +0200 Subject: [PATCH 28/30] Fix bug on communes --- atlas/atlasRoutes.py | 3 +-- atlas/modeles/repositories/vmObservationsMaillesRepository.py | 4 ++-- atlas/modeles/repositories/vmTaxonsRepository.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/atlas/atlasRoutes.py b/atlas/atlasRoutes.py index 65c1bd36e..bd8f1363c 100644 --- a/atlas/atlasRoutes.py +++ b/atlas/atlasRoutes.py @@ -205,8 +205,7 @@ def ficheCommune(insee): connection, current_app.config["NB_LAST_OBS"], insee ) - # listTaxons = vmTaxonsRepository.getTaxonsCommunes(connection, insee) - listTaxons = vmTaxonsRepository.get_taxons_from_obs(connection, observations) + listTaxons = vmTaxonsRepository.getTaxonsCommunes(connection, insee) observers = vmObservationsRepository.getObserversCommunes(connection, insee) diff --git a/atlas/modeles/repositories/vmObservationsMaillesRepository.py b/atlas/modeles/repositories/vmObservationsMaillesRepository.py index 26b92c801..87b4d0683 100644 --- a/atlas/modeles/repositories/vmObservationsMaillesRepository.py +++ b/atlas/modeles/repositories/vmObservationsMaillesRepository.py @@ -128,7 +128,7 @@ def lastObservationsCommuneMaille(connection, mylimit, insee): def getObservationsTaxonCommuneMaille(connection, insee, cd_ref): sql = """ SELECT - o.cd_ref, o.id_maille, o.id_type, o.geojson_maille, o.the_geom, + o.cd_ref, o.id_maille, o.type_code, o.geojson_maille, o.the_geom, extract(YEAR FROM o.dateobs) as annee FROM atlas.vm_observations_mailles o JOIN atlas.vm_communes c @@ -141,7 +141,7 @@ def getObservationsTaxonCommuneMaille(connection, insee, cd_ref): for o in observations: temp = { "id_maille": o.id_maille, - "id_type": o.id_type, + "type_code": o.type_code, "nb_observations": 1, "annee": o.annee, "geojson_maille": json.loads(o.geojson_maille), diff --git a/atlas/modeles/repositories/vmTaxonsRepository.py b/atlas/modeles/repositories/vmTaxonsRepository.py index d92768701..f569b7402 100644 --- a/atlas/modeles/repositories/vmTaxonsRepository.py +++ b/atlas/modeles/repositories/vmTaxonsRepository.py @@ -6,7 +6,7 @@ from sqlalchemy.sql import text from .. import utils - +#FIXME : cette fonction n'a pas d'utilité def get_taxons_from_obs(connection, observations: dict): """ Gets infos on taxons present in the observations From 6a48d89c90820410f2b11b4213060073d209ba7a Mon Sep 17 00:00:00 2001 From: TheoLechemia Date: Tue, 20 Dec 2022 15:58:13 +0100 Subject: [PATCH 29/30] up --- atlas/configuration/settings.ini.sample | 4 ++-- install_db.sh | 2 +- requirements.txt | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/atlas/configuration/settings.ini.sample b/atlas/configuration/settings.ini.sample index 56e4b845a..821b46e3f 100644 --- a/atlas/configuration/settings.ini.sample +++ b/atlas/configuration/settings.ini.sample @@ -97,8 +97,8 @@ limit_shp=/home/`whoami`/atlas/data/ref/territoire.shp # Mon territoire se situe en métropole ? Dans ce cas, on utilise les mailles fournies par l'INPN metropole=true -# Choisissez alors la taille de vos mailles à utiliser (en km) / Valeurs possibles 1, 5 ou 10 -taillemaille=5 +# Choisissez alors la taille de vos mailles à utiliser (en km) / Valeurs possibles M1, M5 ou M10 +taillemaille=M5 # Si 'metropole=false', rajoutez dans le dossier /data/ref un SHP des mailles de votre territoire et renseignez son chemin chemin_custom_maille=/home/`whoami`/atlas/data/ref/custom_maille.shp diff --git a/install_db.sh b/install_db.sh index 0d0604d5f..339d8e0f6 100755 --- a/install_db.sh +++ b/install_db.sh @@ -350,7 +350,7 @@ then sudo sed -i "s/INSERT_ALTITUDE/${insert}/" /tmp/atlas.sql - export PGPASSWORD=$owner_atlas_pass;psql -d $db_name -U $owner_atlas -h $db_host -v default_maille=$type_maille -f /tmp/atlas.sql &>> log/install_db.log + export PGPASSWORD=$owner_atlas_pass;psql -d $db_name -U $owner_atlas -h $db_host -v default_maille=$taillemaille -f /tmp/atlas.sql &>> log/install_db.log sudo -n -u postgres -s psql -d $db_name -c "ALTER TABLE atlas.bib_altitudes OWNER TO "$owner_atlas";" sudo -n -u postgres -s psql -d $db_name -c "ALTER TABLE atlas.bib_taxref_rangs OWNER TO "$owner_atlas";" sudo -n -u postgres -s psql -d $db_name -c "ALTER TABLE atlas.bib_taxref_rangs OWNER TO "$owner_atlas";" diff --git a/requirements.txt b/requirements.txt index 2f620c72f..52eb82213 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -Click==7.0 -dominate==2.3.5 +# Click==7.0 +# dominate==2.3.5 Flask-Compress==1.4.0 Flask-Script==2.0.6 Flask-SQLAlchemy==2.4.0 @@ -7,12 +7,12 @@ Flask==1.1.1 GeoAlchemy2==0.6.3 geojson==2.4.1 gunicorn==19.9.0 -itsdangerous==1.1.0 -Jinja2==2.10.1 -MarkupSafe==1.1.1 +# itsdangerous==1.1.0 +# Jinja2==2.10.1 +# MarkupSafe==1.1.1 marshmallow==2.19.5 psycopg2==2.8.5 SQLAlchemy==1.3.19 -visitor==0.1.3 -Werkzeug==0.15.4 - +# visitor==0.1.3 +# Werkzeug==0.15.4 +Jinja2 From 28d005f9baf46ab0e209a4ad587e387a23a2f1cf Mon Sep 17 00:00:00 2001 From: TheoLechemia Date: Tue, 20 Dec 2022 15:59:57 +0100 Subject: [PATCH 30/30] add missing unique index for refresh --- data/atlas.sql | 3 +++ data/observations_mailles.sql | 3 +++ 2 files changed, 6 insertions(+) diff --git a/data/atlas.sql b/data/atlas.sql index 856d02564..5c1edada5 100644 --- a/data/atlas.sql +++ b/data/atlas.sql @@ -39,6 +39,8 @@ AS SELECT sa.id_synthese, WHERE (t.type_code::text = ANY (ARRAY['M1'::character varying, 'M5'::character varying, 'M10'::character varying]::text[])) AND (NOT sensi.cd_nomenclature::text = '4'::TEXT OR sensi.cd_nomenclature IS NULL ) WITH DATA; +CREATE UNIQUE INDEX i_vm_cor_area_synthese ON atlas.vm_cor_area_synthese USING btree (id_synthese, id_area ); + --Toutes les observations --DROP materialized view atlas.vm_observations; @@ -498,6 +500,7 @@ CREATE OR REPLACE FUNCTION atlas.refresh_materialized_view_data() RETURNS VOID AS $$ BEGIN + REFRESH MATERIALIZED VIEW CONCURRENTLY atlas.vm_cor_area_synthese; REFRESH MATERIALIZED VIEW CONCURRENTLY atlas.vm_observations; REFRESH MATERIALIZED VIEW CONCURRENTLY atlas.vm_observations_mailles; REFRESH MATERIALIZED VIEW CONCURRENTLY atlas.vm_mois; diff --git a/data/observations_mailles.sql b/data/observations_mailles.sql index ceb68972f..77b9a3adb 100644 --- a/data/observations_mailles.sql +++ b/data/observations_mailles.sql @@ -17,6 +17,9 @@ JOIN atlas.vm_cor_area_synthese cor ON cor.id_synthese = obs.id_observation AND create index on atlas.vm_observations_mailles (id_maille); create index on atlas.vm_observations_mailles (cd_ref); + +CREATE UNIQUE INDEX i_vm_observations_mailles ON atlas.vm_observations_mailles USING btree (id_observation, id_maille ); + -- create index on atlas.vm_observations_mailles (geojson_maille); -- This line produces this error : -- SQL Error [54000]: ERROR: index row requires 8400 bytes, maximum size is 8191