Use new optional layers in maps module
This commit is contained in:
parent
eaa9b1c071
commit
1af0d30d94
14 changed files with 280 additions and 184 deletions
3
app/javascript/components/shared/mapbox/Mapbox.js
Normal file
3
app/javascript/components/shared/mapbox/Mapbox.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import ReactMapboxGl from 'react-mapbox-gl';
|
||||
|
||||
export default ReactMapboxGl({});
|
81
app/javascript/components/shared/mapbox/SwitchMapStyle.js
Normal file
81
app/javascript/components/shared/mapbox/SwitchMapStyle.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ortho from './styles/images/preview-ortho.png';
|
||||
import vector from './styles/images/preview-vector.png';
|
||||
|
||||
const STYLES = {
|
||||
ortho: {
|
||||
title: 'Satellite',
|
||||
preview: ortho,
|
||||
color: '#fff'
|
||||
},
|
||||
vector: {
|
||||
title: 'Vectoriel',
|
||||
preview: vector,
|
||||
color: '#000'
|
||||
}
|
||||
};
|
||||
|
||||
const IGN_STYLES = {
|
||||
...STYLES,
|
||||
ign: {
|
||||
title: 'Carte IGN',
|
||||
preview: vector,
|
||||
color: '#000'
|
||||
}
|
||||
};
|
||||
|
||||
function getNextStyle(style, ign) {
|
||||
const styles = Object.keys(ign ? IGN_STYLES : STYLES);
|
||||
let index = styles.indexOf(style) + 1;
|
||||
if (index === styles.length) {
|
||||
return styles[0];
|
||||
}
|
||||
return styles[index];
|
||||
}
|
||||
|
||||
function SwitchMapStyle({ style, setStyle, ign }) {
|
||||
const nextStyle = getNextStyle(style, ign);
|
||||
const { title, preview, color } = (ign ? IGN_STYLES : STYLES)[nextStyle];
|
||||
|
||||
const imgStyle = {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
cursor: 'pointer'
|
||||
};
|
||||
|
||||
const textStyle = {
|
||||
position: 'relative',
|
||||
bottom: '26px',
|
||||
left: '4px',
|
||||
color
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="style-switch"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0
|
||||
}}
|
||||
onClick={() => setStyle(nextStyle)}
|
||||
>
|
||||
<div className="switch-style mapboxgl-ctrl-swith-map-style">
|
||||
<img alt={title} style={imgStyle} src={preview} />
|
||||
<div className="text" style={textStyle}>
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SwitchMapStyle.propTypes = {
|
||||
style: PropTypes.string,
|
||||
setStyle: PropTypes.func,
|
||||
ign: PropTypes.bool
|
||||
};
|
||||
|
||||
export default SwitchMapStyle;
|
213
app/javascript/components/shared/mapbox/styles/base.js
Normal file
213
app/javascript/components/shared/mapbox/styles/base.js
Normal file
|
@ -0,0 +1,213 @@
|
|||
import cadastreLayers from './cadastre-layers';
|
||||
|
||||
const IGN_TOKEN = 'rc1egnbeoss72hxvd143tbyk';
|
||||
|
||||
function ignServiceURL(layer, format = 'image/png') {
|
||||
const url = `https://wxs.ign.fr/${IGN_TOKEN}/geoportail/wmts`;
|
||||
const query =
|
||||
'service=WMTS&request=GetTile&version=1.0.0&tilematrixset=PM&tilematrix={z}&tilecol={x}&tilerow={y}&style=normal';
|
||||
|
||||
return `${url}?${query}&layer=${layer}&format=${format}`;
|
||||
}
|
||||
|
||||
const OPTIONAL_LAYERS = [
|
||||
{
|
||||
label: 'UNESCO',
|
||||
id: 'unesco',
|
||||
layers: [
|
||||
['Aires protégées Géoparcs', 'PROTECTEDAREAS.GP'],
|
||||
['Réserves de biosphère', 'PROTECTEDAREAS.BIOS']
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Arrêtés de protection',
|
||||
id: 'arretes_protection',
|
||||
layers: [
|
||||
['Arrêtés de protection de biotope', 'PROTECTEDAREAS.APB'],
|
||||
['Arrêtés de protection de géotope', 'PROTECTEDAREAS.APG']
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Conservatoire du Littoral',
|
||||
id: 'conservatoire_littoral',
|
||||
layers: [
|
||||
[
|
||||
'Conservatoire du littoral : parcelles protégées',
|
||||
'PROTECTEDAREAS.MNHN.CDL.PARCELS'
|
||||
],
|
||||
[
|
||||
'Conservatoire du littoral : périmètres d’intervention',
|
||||
'PROTECTEDAREAS.MNHN.CDL.PERIMETER'
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Réserves nationales de chasse et de faune sauvage',
|
||||
id: 'reserves_chasse_faune_sauvage',
|
||||
layers: [
|
||||
[
|
||||
'Réserves nationales de chasse et de faune sauvage',
|
||||
'PROTECTEDAREAS.RNCF'
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Réserves biologiques',
|
||||
id: 'reserves_biologiques',
|
||||
layers: [['Réserves biologiques', 'PROTECTEDAREAS.RB']]
|
||||
},
|
||||
{
|
||||
label: 'Réserves naturelles',
|
||||
id: 'reserves_naturelles',
|
||||
layers: [
|
||||
['Réserves naturelles nationales', 'PROTECTEDAREAS.RN'],
|
||||
[
|
||||
'Périmètres de protection de réserves naturelles',
|
||||
'PROTECTEDAREAS.MNHN.RN.PERIMETER'
|
||||
],
|
||||
['Réserves naturelles de Corse', 'PROTECTEDAREAS.RNC'],
|
||||
[
|
||||
'Réserves naturelles régionales',
|
||||
'PROTECTEDSITES.MNHN.RESERVES-REGIONALES'
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Natura 2000',
|
||||
id: 'natura_2000',
|
||||
layers: [
|
||||
['Sites Natura 2000 (Directive Habitats)', 'PROTECTEDAREAS.SIC'],
|
||||
['Sites Natura 2000 (Directive Oiseaux)', 'PROTECTEDAREAS.ZPS']
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Zones humides d’importance internationale',
|
||||
id: 'zones_humides',
|
||||
layers: [
|
||||
['Zones humides d’importance internationale', 'PROTECTEDAREAS.RAMSAR']
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'ZNIEFF',
|
||||
id: 'znieff',
|
||||
layers: [
|
||||
[
|
||||
'Zones naturelles d’intérêt écologique faunistique et floristique de type 1 (ZNIEFF 1 mer)',
|
||||
'PROTECTEDAREAS.ZNIEFF1.SEA'
|
||||
],
|
||||
[
|
||||
'Zones naturelles d’intérêt écologique faunistique et floristique de type 1 (ZNIEFF 1)',
|
||||
'PROTECTEDAREAS.ZNIEFF1'
|
||||
],
|
||||
[
|
||||
'Zones naturelles d’intérêt écologique faunistique et floristique de type 2 (ZNIEFF 2 mer)',
|
||||
'PROTECTEDAREAS.ZNIEFF2.SEA'
|
||||
],
|
||||
[
|
||||
'Zones naturelles d’intérêt écologique faunistique et floristique de type 2 (ZNIEFF 2)',
|
||||
'PROTECTEDAREAS.ZNIEFF2'
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Cadastre',
|
||||
id: 'cadastres',
|
||||
layers: [['Cadastre', 'CADASTRE']]
|
||||
}
|
||||
];
|
||||
|
||||
function buildSources() {
|
||||
return Object.fromEntries(
|
||||
OPTIONAL_LAYERS.flatMap(({ layers }) => layers).map(([, code]) => [
|
||||
code.toLowerCase().replace(/\./g, '-'),
|
||||
rasterSource([ignServiceURL(code)], 'IGN-F/Géoportail/MNHN')
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
function rasterSource(tiles, attribution) {
|
||||
return {
|
||||
type: 'raster',
|
||||
tiles,
|
||||
tileSize: 256,
|
||||
attribution,
|
||||
minzoom: 0,
|
||||
maxzoom: 18
|
||||
};
|
||||
}
|
||||
|
||||
export function buildLayers(ids) {
|
||||
return OPTIONAL_LAYERS.filter(({ id }) => ids.includes(id))
|
||||
.flatMap(({ layers }) => layers)
|
||||
.map(([, code]) =>
|
||||
code === 'CADASTRE'
|
||||
? cadastreLayers
|
||||
: rasterLayer(code.toLowerCase().replace(/\./g, '-'))
|
||||
);
|
||||
}
|
||||
|
||||
export function rasterLayer(source) {
|
||||
return {
|
||||
id: source,
|
||||
source,
|
||||
type: 'raster',
|
||||
paint: { 'raster-resampling': 'linear' }
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
version: 8,
|
||||
metadat: {
|
||||
'mapbox:autocomposite': false,
|
||||
'mapbox:groups': {
|
||||
1444849242106.713: { collapsed: false, name: 'Places' },
|
||||
1444849334699.1902: { collapsed: true, name: 'Bridges' },
|
||||
1444849345966.4436: { collapsed: false, name: 'Roads' },
|
||||
1444849354174.1904: { collapsed: true, name: 'Tunnels' },
|
||||
1444849364238.8171: { collapsed: false, name: 'Buildings' },
|
||||
1444849382550.77: { collapsed: false, name: 'Water' },
|
||||
1444849388993.3071: { collapsed: false, name: 'Land' }
|
||||
},
|
||||
'mapbox:type': 'template',
|
||||
'openmaptiles:mapbox:owner': 'openmaptiles',
|
||||
'openmaptiles:mapbox:source:url': 'mapbox://openmaptiles.4qljc88t',
|
||||
'openmaptiles:version': '3.x',
|
||||
'maputnik:renderer': 'mbgljs'
|
||||
},
|
||||
center: [0, 0],
|
||||
zoom: 1,
|
||||
bearing: 0,
|
||||
pitch: 0,
|
||||
sources: {
|
||||
'decoupage-administratif': {
|
||||
type: 'vector',
|
||||
url:
|
||||
'https://openmaptiles.geo.data.gouv.fr/data/decoupage-administratif.json'
|
||||
},
|
||||
openmaptiles: {
|
||||
type: 'vector',
|
||||
url: 'https://openmaptiles.geo.data.gouv.fr/data/france-vector.json'
|
||||
},
|
||||
'photographies-aeriennes': {
|
||||
type: 'raster',
|
||||
tiles: [
|
||||
'https://tiles.geo.api.gouv.fr/photographies-aeriennes/tiles/{z}/{x}/{y}'
|
||||
],
|
||||
tileSize: 256,
|
||||
attribution: 'Images aériennes © IGN',
|
||||
minzoom: 0,
|
||||
maxzoom: 19
|
||||
},
|
||||
cadastre: {
|
||||
type: 'vector',
|
||||
url: 'https://openmaptiles.geo.data.gouv.fr/data/cadastre.json'
|
||||
},
|
||||
'plan-ign': rasterSource(
|
||||
[ignServiceURL('GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2')],
|
||||
'IGN-F/Géoportail'
|
||||
),
|
||||
...buildSources()
|
||||
},
|
||||
sprite: 'https://openmaptiles.github.io/osm-bright-gl-style/sprite',
|
||||
glyphs: 'https://openmaptiles.geo.data.gouv.fr/fonts/{fontstack}/{range}.pbf'
|
||||
};
|
|
@ -0,0 +1,106 @@
|
|||
export default [
|
||||
{
|
||||
id: 'batiments-line',
|
||||
type: 'line',
|
||||
source: 'cadastre',
|
||||
'source-layer': 'batiments',
|
||||
minzoom: 16,
|
||||
maxzoom: 22,
|
||||
layout: { visibility: 'visible' },
|
||||
paint: {
|
||||
'line-opacity': 1,
|
||||
'line-color': 'rgba(0, 0, 0, 1)',
|
||||
'line-width': 1
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'batiments-fill',
|
||||
type: 'fill',
|
||||
source: 'cadastre',
|
||||
'source-layer': 'batiments',
|
||||
layout: { visibility: 'visible' },
|
||||
paint: {
|
||||
'fill-color': 'rgba(150, 150, 150, 1)',
|
||||
'fill-opacity': {
|
||||
stops: [
|
||||
[16, 0],
|
||||
[17, 0.6]
|
||||
]
|
||||
},
|
||||
'fill-antialias': true
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'parcelles',
|
||||
type: 'line',
|
||||
source: 'cadastre',
|
||||
'source-layer': 'parcelles',
|
||||
minzoom: 15.5,
|
||||
maxzoom: 24,
|
||||
layout: {
|
||||
visibility: 'visible',
|
||||
'line-cap': 'butt',
|
||||
'line-join': 'miter',
|
||||
'line-miter-limit': 2
|
||||
},
|
||||
paint: {
|
||||
'line-color': 'rgba(255, 255, 255, 1)',
|
||||
'line-opacity': 0.8,
|
||||
'line-width': {
|
||||
stops: [
|
||||
[16, 1.5],
|
||||
[17, 2]
|
||||
]
|
||||
},
|
||||
'line-offset': 0,
|
||||
'line-blur': 0,
|
||||
'line-translate': [0, 1],
|
||||
'line-dasharray': [1],
|
||||
'line-gap-width': 0
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'parcelles-fill',
|
||||
type: 'fill',
|
||||
source: 'cadastre',
|
||||
'source-layer': 'parcelles',
|
||||
layout: {
|
||||
visibility: 'visible'
|
||||
},
|
||||
paint: {
|
||||
'fill-color': 'rgba(129, 123, 0, 1)',
|
||||
'fill-opacity': [
|
||||
'case',
|
||||
['boolean', ['feature-state', 'hover'], false],
|
||||
0.7,
|
||||
0.1
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'parcelle-highlighted',
|
||||
type: 'fill',
|
||||
source: 'cadastre',
|
||||
'source-layer': 'parcelles',
|
||||
filter: ['==', 'id', ''],
|
||||
paint: {
|
||||
'fill-color': 'rgba(1, 129, 0, 1)',
|
||||
'fill-opacity': 0.7
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'sections',
|
||||
type: 'line',
|
||||
source: 'cadastre',
|
||||
'source-layer': 'sections',
|
||||
minzoom: 12,
|
||||
layout: { visibility: 'visible' },
|
||||
paint: {
|
||||
'line-color': 'rgba(0, 0, 0, 1)',
|
||||
'line-opacity': 0.7,
|
||||
'line-width': 2,
|
||||
'line-dasharray': [3, 3],
|
||||
'line-translate': [0, 0]
|
||||
}
|
||||
}
|
||||
];
|
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
29
app/javascript/components/shared/mapbox/styles/index.js
Normal file
29
app/javascript/components/shared/mapbox/styles/index.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import baseStyle, { rasterLayer, buildLayers } from './base';
|
||||
import orthoStyle from './ortho-style';
|
||||
import vectorStyle from './vector-style';
|
||||
|
||||
export function getMapStyle(style, optionalLayers) {
|
||||
const mapStyle = { ...baseStyle };
|
||||
|
||||
switch (style) {
|
||||
case 'ortho':
|
||||
mapStyle.layers = orthoStyle;
|
||||
mapStyle.id = 'ortho';
|
||||
mapStyle.name = 'Photographies aériennes';
|
||||
break;
|
||||
case 'vector':
|
||||
mapStyle.layers = vectorStyle;
|
||||
mapStyle.id = 'vector';
|
||||
mapStyle.name = 'Carte OSM';
|
||||
break;
|
||||
case 'ign':
|
||||
mapStyle.layers = [rasterLayer('plan-ign')];
|
||||
mapStyle.id = 'ign';
|
||||
mapStyle.name = 'Carte IGN';
|
||||
break;
|
||||
}
|
||||
|
||||
mapStyle.layers = mapStyle.layers.concat(buildLayers(optionalLayers));
|
||||
|
||||
return mapStyle;
|
||||
}
|
2639
app/javascript/components/shared/mapbox/styles/ortho-style.js
Normal file
2639
app/javascript/components/shared/mapbox/styles/ortho-style.js
Normal file
File diff suppressed because it is too large
Load diff
2839
app/javascript/components/shared/mapbox/styles/vector-style.js
Normal file
2839
app/javascript/components/shared/mapbox/styles/vector-style.js
Normal file
File diff suppressed because it is too large
Load diff
78
app/javascript/components/shared/mapbox/utils.js
Normal file
78
app/javascript/components/shared/mapbox/utils.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
import { LngLatBounds } from 'mapbox-gl';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export function getBounds(geometry) {
|
||||
const bbox = new LngLatBounds();
|
||||
|
||||
if (geometry.type === 'Point') {
|
||||
return [geometry.coordinates, geometry.coordinates];
|
||||
} else if (geometry.type === 'LineString') {
|
||||
for (const coordinate of geometry.coordinates) {
|
||||
bbox.extend(coordinate);
|
||||
}
|
||||
} else {
|
||||
for (const coordinate of geometry.coordinates[0]) {
|
||||
bbox.extend(coordinate);
|
||||
}
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
export function fitBounds(map, feature) {
|
||||
if (map) {
|
||||
map.fitBounds(getBounds(feature.geometry), { padding: 100 });
|
||||
}
|
||||
}
|
||||
|
||||
export function findFeature(featureCollection, id) {
|
||||
return featureCollection.features.find(
|
||||
(feature) => feature.properties.id === id
|
||||
);
|
||||
}
|
||||
|
||||
export function filterFeatureCollection(featureCollection, source) {
|
||||
return {
|
||||
type: 'FeatureCollection',
|
||||
features: featureCollection.features.filter(
|
||||
(feature) => feature.properties.source === source
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
export function filterFeatureCollectionByGeometryType(featureCollection, type) {
|
||||
return {
|
||||
type: 'FeatureCollection',
|
||||
features: featureCollection.features.filter(
|
||||
(feature) => feature.geometry.type === type
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
export function noop() {}
|
||||
|
||||
export function generateId() {
|
||||
return Math.random().toString(20).substr(2, 6);
|
||||
}
|
||||
|
||||
export function useEvent(eventName, callback) {
|
||||
return useEffect(() => {
|
||||
addEventListener(eventName, callback);
|
||||
return () => removeEventListener(eventName, callback);
|
||||
}, [eventName, callback]);
|
||||
}
|
||||
|
||||
export function getCenter(geometry, lngLat) {
|
||||
const bbox = new LngLatBounds();
|
||||
|
||||
switch (geometry.type) {
|
||||
case 'Point':
|
||||
return [...geometry.coordinates];
|
||||
case 'LineString':
|
||||
return [lngLat.lng, lngLat.lat];
|
||||
default:
|
||||
for (const coordinate of geometry.coordinates[0]) {
|
||||
bbox.extend(coordinate);
|
||||
}
|
||||
return bbox.getCenter();
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue