loko/streetup/documents/templates/documents/document_map_view.html
2026-07-22 14:48:40 +02:00

218 lines
8.6 KiB
HTML

{% extends "documents/base_documents.html" %}
{% load i18n static documents_tags %}
{% block title %}
{% translate "Plan géolocalisé" %} - {{ document.title }}
{% endblock title %}
{% block head %}
{{ block.super }}
<style>
#map .maplibregl-ctrl-top-right,
#map .mapboxgl-ctrl-top-right {
top: 10px !important;
}
.map-header {
background: linear-gradient(135deg, #0284c7, #0369a1);
color: #fff;
padding: 1rem 1.5rem;
border-radius: 8px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
margin-bottom: 1.5rem;
}
.map-container {
height: 680px;
width: 100%;
border-radius: 8px;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
position: relative;
}
.map-control-card {
position: absolute;
top: 20px;
right: 20px;
z-index: 10;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(8px);
border-radius: 8px;
width: 250px;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
</style>
{% endblock head %}
{% block content %}
<div class="container-fluid py-4">
<!-- Header -->
<div class="map-header d-flex justify-content-between align-items-center flex-wrap gap-3">
<div>
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-1">
<li class="breadcrumb-item"><a href="{% url 'documents:list' %}" class="text-white-50">{% translate "Documents" %}</a></li>
<li class="breadcrumb-item"><a href="{% url 'documents:detail' document.pk %}" class="text-white-50">{{ document.title }}</a></li>
<li class="breadcrumb-item active text-white" aria-current="page">{% translate "Visualisation" %}</li>
</ol>
</nav>
<h3 class="mb-0 fw-bold">
<i class="bi bi-map-fill"></i> {% translate "Visualisation sur la carte" %}
</h3>
</div>
<div>
<a href="{% url 'documents:detail' document.pk %}" class="btn btn-outline-light btn-sm px-4">
<i class="bi bi-arrow-left"></i> {% translate "Retour au document" %}
</a>
</div>
</div>
<!-- Map Area -->
<div class="card border-0 mb-4 shadow-sm">
<div class="card-body p-0 position-relative">
<div id="map" class="map-container"></div>
<!-- Map Controls Panel -->
<div class="map-control-card p-3 border">
<h6 class="fw-bold mb-2 text-dark"><i class="bi bi-sliders me-2 text-primary"></i>{% translate "Contrôles" %}</h6>
<div class="mb-2">
<label for="opacityRange" class="form-label small fw-bold text-secondary">{% translate "Opacité du plan" %}</label>
<input type="range" class="form-range" id="opacityRange" min="0" max="1" step="0.05" value="0.7">
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block script %}
{{ block.super }}
<script src="{% static 'common/layer-switcher-control.js' %}"></script>
<script src="{% static 'common/address_search_control.js' %}"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
const pageImageUrl = "{% url 'documents:page_image' document.pk document.georeference_page %}";
const coords = {{ document.georeference_coords|safe }};
let mapStyle = window.MAP_STYLE_LIGHT;
if (!mapStyle) {
mapStyle = "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json";
}
// Initialize Map
const map = new maplibregl.Map({
container: 'map',
style: mapStyle,
center: [coords[0][0], coords[0][1]],
zoom: 16
});
map.addControl(new maplibregl.NavigationControl(), 'top-right');
// Keep overlay layer always on top of base layers and orthophotos
map.on('styledata', () => {
if (map.getLayer('overlay-lyr')) {
const layers = map.getStyle().layers;
if (layers && layers.length > 0 && layers[layers.length - 1].id !== 'overlay-lyr') {
map.moveLayer('overlay-lyr');
}
}
});
// Add Geocoding/Address search control
if (typeof AddressSearchControl !== 'undefined') {
const searchCtrl = new AddressSearchControl({
autocompleteUrl: "{% url 'common:autocomplete_street' %}"
});
map.addControl(searchCtrl, 'top-left');
searchCtrl.addEventListener('address-selected', function(e) {
if (e.detail && e.detail.lon && e.detail.lat) {
map.flyTo({
center: [e.detail.lon, e.detail.lat],
zoom: 17
});
}
});
}
// Add Satellite Layer Switcher Control
if (typeof LayerSwitcherControl !== 'undefined') {
const layerSwitcher = new LayerSwitcherControl({
layers: [
{
id: 'ortho',
name: 'Orthophotos',
icon: 'satellite',
group: 'ortho',
layer: {
type: 'raster',
paint: { 'raster-opacity': 1 }
},
sublayers: [
{
id: 'urbis',
name: window.MAP_ORTHO_WMS_NAME || 'Urbis (Bruxelles)',
default: true,
source: {
type: 'raster',
tiles: [
window.MAP_ORTHO_WMS_URL || 'https://geoservices-urbis.irisnet.be/geoserver/urbisgrid/wms?service=WMS&version=1.3.0&request=GetMap&layers=Ortho&styles=&format=image/png&transparent=true&width=256&height=256&crs=EPSG:3857&bbox={bbox-epsg-3857}'
],
tileSize: 256,
attribution: window.MAP_ORTHO_WMS_ATTRIBUTION || '© Brussels UrbIS'
}
},
{
id: 'vlaanderen',
name: 'Vlaanderen 2023',
source: {
type: 'raster',
tiles: [
'https://geo.api.vlaanderen.be/OMW/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=omwrgb23vl&STYLE=&FORMAT=image/png&TILEMATRIXSET=GoogleMapsVL&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}'
],
tileSize: 256,
attribution: '© Digitaal Vlaanderen - Orthofoto 2023'
}
}
]
}
]
});
map.addControl(layerSwitcher, 'top-left');
}
// Zoom map to the coordinates boundaries
const lngs = coords.map(c => c[0]);
const lats = coords.map(c => c[1]);
map.fitBounds([
[Math.min(...lngs), Math.min(...lats)],
[Math.max(...lngs), Math.max(...lats)]
], { padding: 80, maxZoom: 18, animate: false });
map.on('load', function() {
// Add Image source and layer
map.addSource('overlay-src', {
type: 'image',
url: pageImageUrl,
coordinates: coords
});
map.addLayer({
id: 'overlay-lyr',
type: 'raster',
source: 'overlay-src',
paint: {
'raster-opacity': 0.7
}
});
});
// Opacity control
const opacityRange = document.getElementById('opacityRange');
opacityRange.addEventListener('input', function() {
if (map.getLayer('overlay-lyr')) {
map.setPaintProperty('overlay-lyr', 'raster-opacity', parseFloat(this.value));
}
});
});
</script>
{% endblock script %}