365 lines
13 KiB
HTML
365 lines
13 KiB
HTML
{% load static %}
|
|
{% load custom_tags %}
|
|
{% load i18n %}
|
|
|
|
<!-- add MapLibre JavaScript and CSS -->
|
|
<script src="{% settings_value "MAP_TILE_SERVER" %}/assets/lib/maplibre-gl/maplibre-gl.js"></script>
|
|
<link href="{% settings_value "MAP_TILE_SERVER" %}/assets/lib/maplibre-gl/maplibre-gl.css" rel="stylesheet"/>
|
|
|
|
<!-- add Turf see https://maplibre.org/maplibre-gl-js/docs/examples/draw-a-radius/ -->
|
|
<script src="{% static 'fellchensammlung/js/turf.min.js' %}"></script>
|
|
|
|
<!-- add container for the map -->
|
|
<div id="map" class="map"></div>
|
|
|
|
<!-- start map -->
|
|
<script>
|
|
{% if zoom_level %}
|
|
var zoom_level = {{ zoom_level }};
|
|
{% else %}
|
|
var zoom_level = 4;
|
|
{% endif %}
|
|
|
|
{% if map_center %}
|
|
var map_center = [{{ map_center.longitude | pointdecimal }}, {{ map_center.latitude | pointdecimal }}];
|
|
{% else %}
|
|
var map_center = [10.49, 50.68]; <!-- Point middle of Germany -->
|
|
zoom_level = 4; //Overwrite zoom level when no place is found
|
|
{% endif %}
|
|
|
|
let map = new maplibregl.Map({
|
|
container: 'map',
|
|
style: '{% static "fellchensammlung/map/styles/colorful/style.json" %}',
|
|
center: map_center,
|
|
zoom: zoom_level
|
|
});
|
|
|
|
map.addControl(new maplibregl.FullscreenControl());
|
|
map.addControl(new maplibregl.NavigationControl({showCompass: false}));
|
|
|
|
|
|
map.on('load', async () => {
|
|
{% if show_ANs %}
|
|
// Load adoption notices as geojson
|
|
map.addSource('adoption-notices', {
|
|
type: 'geojson',
|
|
data: '{% url "api-adoption-notice-list-geojson" %}',
|
|
cluster: true,
|
|
clusterMaxZoom: 14,
|
|
clusterRadius: 50
|
|
});
|
|
|
|
// Layer for clusters
|
|
map.addLayer({
|
|
id: 'clusters',
|
|
type: 'circle',
|
|
source: 'adoption-notices',
|
|
filter: ['has', 'point_count'],
|
|
paint: {
|
|
'circle-color': [
|
|
'step',
|
|
['get', 'point_count'],
|
|
'#51bbd6',
|
|
100,
|
|
'#f1f075',
|
|
750,
|
|
'#f28cb1'
|
|
],
|
|
'circle-radius': [
|
|
'step',
|
|
['get', 'point_count'],
|
|
20,
|
|
100,
|
|
30,
|
|
750,
|
|
40
|
|
]
|
|
}
|
|
});
|
|
|
|
map.addLayer({
|
|
id: 'cluster-count',
|
|
type: 'symbol',
|
|
source: 'adoption-notices',
|
|
filter: ['has', 'point_count'],
|
|
layout: {
|
|
'text-field': '{point_count_abbreviated}',
|
|
'text-size': 12,
|
|
'text-font': ['open_sans_medium'],
|
|
}
|
|
});
|
|
|
|
const rat_image = await map.loadImage('{% static 'fellchensammlung/img/logo_transparent.png' %}');
|
|
map.addImage('rat', rat_image.data);
|
|
|
|
|
|
map.addLayer({
|
|
id: 'unclustered-point',
|
|
type: 'symbol',
|
|
source: 'adoption-notices',
|
|
filter: ['!', ['has', 'point_count']],
|
|
layout: {
|
|
'icon-image': ['get', 'species'],
|
|
'icon-size': 0.07,
|
|
'icon-allow-overlap': true
|
|
}
|
|
});
|
|
|
|
|
|
|
|
// inspect an AN cluster on click
|
|
map.on('click', 'clusters', async (e) => {
|
|
const features = map.queryRenderedFeatures(e.point, {
|
|
layers: ['clusters']
|
|
});
|
|
const clusterId = features[0].properties.cluster_id;
|
|
const zoom = await map.getSource('adoption-notices').getClusterExpansionZoom(clusterId);
|
|
map.easeTo({
|
|
center: features[0].geometry.coordinates,
|
|
zoom
|
|
});
|
|
});
|
|
|
|
// When a click event occurs on a feature in
|
|
// the unclustered-point layer, open a popup at
|
|
// the location of the feature, with
|
|
// description HTML from its properties.
|
|
map.on('click', 'unclustered-point', (e) => {
|
|
const coordinates = e.features[0].geometry.coordinates.slice();
|
|
const title = e.features[0].properties.title;
|
|
const url = e.features[0].properties.url;
|
|
const description = e.features[0].properties.description;
|
|
const location_hr = e.features[0].properties.location_hr;
|
|
const image_url = e.features[0].properties.image_url;
|
|
const image_alt = e.features[0].properties.image_alt;
|
|
|
|
// Ensure that if the map is zoomed out such that
|
|
// multiple copies of the feature are visible, the
|
|
// popup appears over the copy being pointed to.
|
|
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
|
|
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
|
|
}
|
|
|
|
const description_column = `
|
|
<div class="column">
|
|
<strong><a class="is-size-7" href="${url}">${title}</strong> <i class="fa-solid fa-arrow-up-right-from-square"></i></a><br>
|
|
<span><strong>{% translate 'Ort' %}</strong>: ${location_hr}</span><br>
|
|
<p class="is-size-7">${truncate(description, 80, url)}</p>
|
|
</div>
|
|
`;
|
|
let picture_column = '';
|
|
if (image_url) {
|
|
picture_column = `
|
|
<div class="column">
|
|
<figure class="image is-128x128">
|
|
<img src="${image_url}" alt="${image_alt}"/>
|
|
</figure>
|
|
</div>`;
|
|
}
|
|
|
|
new maplibregl.Popup()
|
|
.setLngLat(coordinates)
|
|
.setHTML(`
|
|
<div class="popup-content is-size-7">
|
|
<div class="columns">
|
|
${description_column}
|
|
${picture_column}
|
|
</div>
|
|
</div>`
|
|
)
|
|
.addTo(map);
|
|
});
|
|
|
|
map.on('mouseenter', 'clusters', () => {
|
|
map.getCanvas().style.cursor = 'pointer';
|
|
});
|
|
map.on('mouseleave', 'clusters', () => {
|
|
map.getCanvas().style.cursor = '';
|
|
});
|
|
{% endif %}
|
|
|
|
{% if show_rescue_orgs %}
|
|
// Load rescue_orgs as geojson
|
|
map.addSource('rescue-orgs', {
|
|
type: 'geojson',
|
|
data: '{% url "api-organization-list-geojson" %}',
|
|
cluster: true,
|
|
clusterMaxZoom: 14,
|
|
clusterRadius: 50
|
|
});
|
|
|
|
// Layer for clusters
|
|
map.addLayer({
|
|
id: 'org-clusters',
|
|
type: 'circle',
|
|
source: 'rescue-orgs',
|
|
filter: ['has', 'point_count'],
|
|
paint: {
|
|
'circle-color': [
|
|
'step',
|
|
['get', 'point_count'],
|
|
'#51bbd6',
|
|
100,
|
|
'#f1f075',
|
|
750,
|
|
'#f28cb1'
|
|
],
|
|
'circle-radius': [
|
|
'step',
|
|
['get', 'point_count'],
|
|
20,
|
|
100,
|
|
30,
|
|
750,
|
|
40
|
|
]
|
|
}
|
|
});
|
|
|
|
map.addLayer({
|
|
id: 'org-cluster-count',
|
|
type: 'symbol',
|
|
source: 'rescue-orgs',
|
|
filter: ['has', 'point_count'],
|
|
layout: {
|
|
'text-field': '{point_count_abbreviated}',
|
|
'text-size': 12,
|
|
'text-font': ['open_sans_medium'],
|
|
}
|
|
});
|
|
|
|
const org_image = await map.loadImage('{% static 'fellchensammlung/img/animal_shelter.png' %}');
|
|
map.addImage('org', org_image.data);
|
|
|
|
|
|
map.addLayer({
|
|
id: 'org-unclustered-point',
|
|
type: 'symbol',
|
|
source: 'rescue-orgs',
|
|
filter: ['!', ['has', 'point_count']],
|
|
layout: {
|
|
'icon-image': 'org',
|
|
'icon-size': 0.07,
|
|
'icon-allow-overlap': true
|
|
}
|
|
});
|
|
|
|
// inspect an org cluster on click
|
|
map.on('click', 'org-clusters', async (e) => {
|
|
const features = map.queryRenderedFeatures(e.point, {
|
|
layers: ['org-clusters']
|
|
});
|
|
const clusterId = features[0].properties.cluster_id;
|
|
const zoom = await map.getSource('rescue-orgs').getClusterExpansionZoom(clusterId);
|
|
map.easeTo({
|
|
center: features[0].geometry.coordinates,
|
|
zoom
|
|
});
|
|
});
|
|
|
|
// Same for orgs
|
|
map.on('click', 'org-unclustered-point', (e) => {
|
|
const coordinates = e.features[0].geometry.coordinates.slice();
|
|
const name = e.features[0].properties.name;
|
|
const url = e.features[0].properties.url;
|
|
const description = e.features[0].properties.description;
|
|
const location_hr = e.features[0].properties.location_hr;
|
|
|
|
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
|
|
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
|
|
}
|
|
|
|
|
|
new maplibregl.Popup()
|
|
.setLngLat(coordinates)
|
|
.setHTML(`
|
|
<div class="popup-content is-size-7">
|
|
<strong><a class="is-size-7" href="${url}">${name}</a></strong> <i class="fa-solid fa-arrow-up-right-from-square"></i><br>
|
|
<span><strong>{% translate 'Ort' %}</strong>: ${location_hr}</span><br>
|
|
<p class="is-size-7">${truncate(description, 80, url)}</p>
|
|
</div>`
|
|
)
|
|
.addTo(map);
|
|
});
|
|
|
|
|
|
map.on('mouseenter', 'org-clusters', () => {
|
|
map.getCanvas().style.cursor = 'pointer';
|
|
});
|
|
map.on('mouseleave', 'org-clusters', () => {
|
|
map.getCanvas().style.cursor = '';
|
|
});
|
|
{% endif %}
|
|
|
|
image = await map.loadImage('{% static "fellchensammlung/img/pin.png" %}');
|
|
map.addImage('pin', image.data);
|
|
{% for map_pin in map_pins %}
|
|
map.addSource('point_{{ forloop.counter }}', {
|
|
'type': 'geojson',
|
|
'data': {
|
|
'type': 'FeatureCollection',
|
|
'features': [
|
|
{
|
|
'type': 'Feature',
|
|
'geometry': {
|
|
'type': 'Point',
|
|
'coordinates': [{{ map_pin.location.longitude | pointdecimal }}, {{ map_pin.location.latitude | pointdecimal }}]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
});
|
|
map.addLayer({
|
|
'id': 'point_{{ forloop.counter }}',
|
|
'type': 'circle',
|
|
'source': 'point_{{ forloop.counter }}',
|
|
'paint': {
|
|
'circle-radius': 18,
|
|
'circle-color': '#ff878980'
|
|
}
|
|
});
|
|
{% endfor %}
|
|
});
|
|
|
|
{% if search_center %}
|
|
var search_center = [{{ search_center.longitude | pointdecimal }}, {{ search_center.latitude | pointdecimal }}];
|
|
map.on('load', () => {
|
|
const radius = {{ search_radius }}; // kilometer
|
|
const options = {
|
|
steps: 64,
|
|
units: 'kilometers'
|
|
};
|
|
const circle = turf.circle(search_center, radius, options);
|
|
|
|
// Add the circle as a GeoJSON source
|
|
map.addSource('location-radius', {
|
|
type: 'geojson',
|
|
data: circle
|
|
});
|
|
|
|
// Add a fill layer with some transparency
|
|
map.addLayer({
|
|
id: 'location-radius',
|
|
type: 'fill',
|
|
source: 'location-radius',
|
|
paint: {
|
|
'fill-color': 'rgba(140,207,255,0.3)',
|
|
'fill-opacity': 0.5
|
|
}
|
|
});
|
|
|
|
// Add a line layer to draw the circle outline
|
|
map.addLayer({
|
|
id: 'location-radius-outline',
|
|
type: 'line',
|
|
source: 'location-radius',
|
|
paint: {
|
|
'line-color': '#0094ff',
|
|
'line-width': 3
|
|
}
|
|
});
|
|
});
|
|
{% endif %}
|
|
|
|
</script>
|