kortforsyningen maps (#484)

* add maps app, add proxy view for kortforsyningen services, add static_src/js/kfmap.js to create leaflet maps with tiles from kortforsyningen, adjust facility views to use the new map, add marker field to FacilityType model to specify marker colour, add js and marker pngs for coloured markers

* remove debug print

Co-authored-by: Thomas Steen Rasmussen <tykling@bornhack.org>
This commit is contained in:
Thomas Steen Rasmussen 2020-03-28 11:45:54 +01:00 committed by GitHub
parent 3af0ef4aab
commit 3c6fadcf70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 686 additions and 36 deletions

View File

@ -95,4 +95,4 @@ ECONOMYTEAM_EMAIL = "{{ django_economyteam_email }}"
ECONOMYTEAM_NAME = "Economy"
BORNHACK_2019_OLD_TOKEN_TOKEN = "{{ bornhack_2019_old_token_token }}"
KORTFORSYNINGEN_TOKEN = "{{ kortforsyningen_token }}"

View File

@ -54,6 +54,7 @@ INSTALLED_APPS = [
"wishlist",
"facilities",
"phonebook",
"maps",
"allauth",
"allauth.account",
"allauth_2fa",

View File

@ -53,6 +53,7 @@ urlpatterns = [
path("api/", csrf_exempt(GraphQLView.as_view(graphiql=True))),
path("camps/", CampListView.as_view(), name="camp_list"),
path("token/", include("tokens.urls", namespace="tokens")),
path("maps/", include("maps.urls", namespace="maps")),
# camp redirect views here
path(
"",

View File

@ -12,8 +12,8 @@ class FacilityQuickFeedbackAdmin(admin.ModelAdmin):
@admin.register(FacilityType)
class FacilityTypeAdmin(admin.ModelAdmin):
list_display = ["name", "description", "responsible_team", "camp"]
list_filter = ["responsible_team__camp", "responsible_team"]
list_display = ["name", "description", "icon", "marker", "responsible_team", "camp"]
list_filter = ["responsible_team__camp", "icon", "marker", "responsible_team"]
@admin.register(Facility)

View File

@ -0,0 +1,33 @@
# Generated by Django 3.0.3 on 2020-03-10 11:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("facilities", "0003_location_not_null"),
]
operations = [
migrations.AddField(
model_name="facilitytype",
name="marker",
field=models.CharField(
choices=[
("blueIcon", "Blue (#2A81CB)"),
("goldIcon", "Gold (#FFD326)"),
("redIcon", "Red (#CB2B3E)"),
("greenIcon", "Green (#2AAD27)"),
("orangeIcon", "Orange (#CB8427)"),
("yellowIcon", "Yellow (#CAC428)"),
("violetIcon", "Violet (#9C2BCB)"),
("greyIcon", "Grey (#7B7B7B)"),
("blackIcon", "Black (#3D3D3D)"),
],
default="blueIcon",
help_text="The name/colour of the Leaflet marker to use for this facility type.",
max_length=10,
),
),
]

View File

@ -8,6 +8,7 @@ from django.core.exceptions import ValidationError
from django.db import models
from django.shortcuts import reverse
from django.utils.text import slugify
from maps.utils import LeafletMarkerChoices
from utils.models import CampRelatedModel, UUIDModel
logger = logging.getLogger("bornhack.%s" % __name__)
@ -59,6 +60,13 @@ class FacilityType(CampRelatedModel):
help_text="Name of the fontawesome icon to use, including the 'fab fa-' or 'fas fa-' part.",
)
marker = models.CharField(
max_length=10,
choices=LeafletMarkerChoices.choices,
default=LeafletMarkerChoices.BLUE,
help_text="The name/colour of the Leaflet marker to use for this facility type.",
)
responsible_team = models.ForeignKey(
"teams.Team",
on_delete=models.PROTECT,

View File

@ -1,9 +1,13 @@
{% extends 'program_base.html' %}
{% load leaflet_tags %}
{% load static %}
{% block extra_head %}
{% leaflet_js %}
{% leaflet_css %}
<script src="{% static 'js/leaflet-1.6.0.js' %}" type="text/javascript"></script>
<script src="{% static 'js/proj4.js' %}" type="text/javascript"></script>
<script src="{% static 'js/proj4leaflet.js' %}" type="text/javascript"></script>
<script src="{% static 'js/leaflet-color-markers.js' %}" type="text/javascript"></script>
{% endblock extra_head %}
{% block title %}
@ -16,26 +20,29 @@
<h3 class="panel-title">{{ facility.facility_type.name }}: {{ facility.name }}</h3>
</div>
<div class="panel-body">
<p class="lead">{{ facility.description }}</p>
<p class="lead">
<i class="{{ facility.facility_type.icon }} fa-2x fa-pull-left fa-fw"></i> {{ facility.name }} {{ facility.description }}</p>
{% if request.user.is_authenticated %}
<a href="{% url "facilities:facility_feedback" camp_slug=camp.slug facility_type_slug=facilitytype.slug facility_uuid=facility.uuid %}" class="btn btn-primary"><i class="fas fa-comment-dots"></i> Submit Feedback</a>
{% endif %}
<a href="{% url "facilities:facility_list" camp_slug=camp.slug facility_type_slug=facilitytype.slug %}" class="btn btn-primary"><i class="fas fa-list"></i> Back to {{ facilitytype.name }} list</a>
<p>{% leaflet_map "facility_detail" %}</p>
<p>
<div id="map" class="map"></div>
</div>
</div>
<script type="text/javascript">
// add a listener to add the marker for the facility on the leaflet map after it inits
window.addEventListener("map:init", function (e) {
var detail = e.detail;
function MapReadyCallback() {
// add a marker for this facility
{% url "facilities:facility_feedback" camp_slug=facility.camp.slug facility_type_slug=facility.facility_type.slug facility_uuid=facility.uuid as feedback %}
marker = L.marker([{{ facility.location.y }}, {{ facility.location.x }}]);
marker.addTo(detail.map);
marker.bindPopup("<b>{{ facility.name }}</b><br><p>{{ facility.description }}</p><p>Responsible team: {{ facility.facility_type.responsible_team.name }} Team</p>{% if request.user.is_authenticated %}<p><a href='{{ feedback }}' class='btn btn-primary' style='color: white;'><i class='fas fa-comment-dots'></i> Feedback</a></p>{% endif %}");
detail.map.setView([{{ facility_list.0.location.y }}, {{ facility_list.0.location.x }}], 15);
}, false);
var marker = L.marker([{{ facility.location.y }}, {{ facility.location.x }}])
marker.bindPopup("<b>{{ facility.name }}</b><br><p>{{ facility.description }}</p><p>Responsible team: {{ facility.facility_type.responsible_team.name }} Team</p>{% if request.user.is_authenticated %}<p><a href='{{ feedback }}' class='btn btn-primary' style='color: white;'><i class='fas fa-comment-dots'></i> Feedback</a></p>{% endif %}").addTo(this);
// max zoom since we have only one marker
this.setView(marker.getLatLng(), 13);
};
</script>
<script src="{% static 'js/kfmap.js' %}" type="text/javascript"></script>
{% endblock %}

View File

@ -1,9 +1,13 @@
{% extends 'program_base.html' %}
{% load leaflet_tags %}
{% load static %}
{% block extra_head %}
{% leaflet_js %}
{% leaflet_css %}
<script src="{% static 'js/leaflet-1.6.0.js' %}" type="text/javascript"></script>
<script src="{% static 'js/proj4.js' %}" type="text/javascript"></script>
<script src="{% static 'js/proj4leaflet.js' %}" type="text/javascript"></script>
<script src="{% static 'js/leaflet-color-markers.js' %}" type="text/javascript"></script>
{% endblock extra_head %}
{% block title %}
@ -25,34 +29,43 @@ Facilities of type {{ facilitytype }} | {{ block.super }}
{% endif %}
<h4 class="list-group-item-heading">
<i class="{{ facility.facility_type.icon }} fa-2x fa-pull-left fa-fw"></i> {{ facility.name }}
<img class="pull-right" src="{% static 'img/leaflet/marker-icon-'|add:facility.facility_type.marker|slice:"-4"|add:'.png' %}">
</h4>
<p class="list-group-item-text">{{ facility.description }}</p>
<p class="list-group-item-text"><i>Location: Lat {{ facility.location.y }} Long {{ facility.location.x }}</i></p>
{% if request.user.is_authenticated %}
</a>
{% else %}
</div>
{% endif %}
{% endfor %}
<p>
<div id="map" class="map"></div>
</div>
<p>{% leaflet_map "facility_list" %}</p>
<a href="{% url "facilities:facility_type_list" camp_slug=camp.slug %}" class="btn btn-primary"><i class="fas fa-list"></i> Back to facility type list</a>
</div>
</div>
<script type="text/javascript">
// add a listener to add the marker for each facility on the leaflet map after it inits
window.addEventListener("map:init", function (e) {
var detail = e.detail;
{% for facility in facility_list %}
function MapReadyCallback() {
// loop over facilities and add a marker for each,
// include a popup in the marker
var markers = new Array();
{% for facility in facility_list %}
{% url "facilities:facility_detail" camp_slug=facility.camp.slug facility_type_slug=facility.facility_type.slug facility_uuid=facility.uuid as detail %}
{% url "facilities:facility_feedback" camp_slug=facility.camp.slug facility_type_slug=facility.facility_type.slug facility_uuid=facility.uuid as feedback %}
marker = L.marker([{{ facility.location.y }}, {{ facility.location.x }}]);
marker.addTo(detail.map);
marker.bindPopup("<b>{{ facility.name }}</b><br><p>{{ facility.description }}</p><p>Responsible team: {{ facility.facility_type.responsible_team.name }} Team</p>{% if request.user.is_authenticated %}<p><a href='{{ detail }}' class='btn btn-primary' style='color: white;'><i class='fas fa-search'></i> Details</a><a href='{{ feedback }}' class='btn btn-primary' style='color: white;'><i class='fas fa-comment-dots'></i> Feedback</a></p>{% endif %}");
marker = L.marker([{{ facility.location.y }}, {{ facility.location.x }}], {icon: {{ facility.facility_type.marker }}}).bindPopup("<b>{{ facility.name }}</b><br><p>{{ facility.description }}</p><p>Responsible team: {{ facility.facility_type.responsible_team.name }} Team</p>{% if request.user.is_authenticated %}<p><a href='{{ detail }}' class='btn btn-primary' style='color: white;'><i class='fas fa-search'></i> Details</a><a href='{{ feedback }}' class='btn btn-primary' style='color: white;'><i class='fas fa-comment-dots'></i> Feedback</a></p>{% endif %}").addTo(this);
markers.push(marker.getLatLng());
{% endfor %}
detail.map.setView([{{ facility_list.0.location.y }}, {{ facility_list.0.location.x }}], 15);
}, false);
var markerBounds = L.latLngBounds(markers);
// fitBounds appears to not respect maxZoom from the tilelayer,
// leading to the "Error: Attempted to load an infinite number of tiles" mess,
// so hardcode maxZoom to 13 here
this.fitBounds(markerBounds, {maxZoom: 13});
};
</script>
<script src="{% static 'js/kfmap.js' %}" type="text/javascript"></script>
{% endblock %}

View File

@ -1,26 +1,63 @@
{% extends 'program_base.html' %}
{% load leaflet_tags %}
{% load static %}
{% block title %}
Facility Types | {{ block.super }}
{% endblock %}
{% block extra_head %}
{% leaflet_css %}
<script src="{% static 'js/leaflet-1.6.0.js' %}" type="text/javascript"></script>
<script src="{% static 'js/proj4.js' %}" type="text/javascript"></script>
<script src="{% static 'js/proj4leaflet.js' %}" type="text/javascript"></script>
<script src="{% static 'js/leaflet-color-markers.js' %}" type="text/javascript"></script>
{% endblock extra_head %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Facility Types</h3>
<h3 class="panel-title">Facilities</h3>
</div>
<div class="panel-body">
<p class="lead">The {{ camp.title }} facilities are grouped in categories. Pick a category to limit the facilities shown.</p>
<div class="list-group">
{% for ft in facilitytype_list %}
<a href="{% url 'facilities:facility_list' camp_slug=camp.slug facility_type_slug=ft.slug %}" class="list-group-item">
<h4 class="list-group-item-heading">
<i class="{{ ft.icon }} fa-2x fa-pull-left fa-fw"></i> {{ ft.name }}
<img class="pull-right" src="{% static 'img/leaflet/marker-icon-'|add:ft.marker|slice:"-4"|add:'.png' %}">
</h4>
<p class="list-group-item-text">{{ ft.description }}</p>
</a>
{% endfor %}
</div>
<p>
<div id="map" class="map"></div>
</div>
</div>
<script type="text/javascript">
function MapReadyCallback() {
// loop over facilities and add a marker for each,
// include a popup in the marker
var markers = new Array();
{% for ft in facilitytype_list %}
{% for facility in ft.facilities.all %}
{% url "facilities:facility_detail" camp_slug=facility.camp.slug facility_type_slug=facility.facility_type.slug facility_uuid=facility.uuid as detail %}
{% url "facilities:facility_feedback" camp_slug=facility.camp.slug facility_type_slug=facility.facility_type.slug facility_uuid=facility.uuid as feedback %}
marker = L.marker([{{ facility.location.y }}, {{ facility.location.x }}], {icon: {{ facility.facility_type.marker }}}).bindPopup("<b>{{ facility.name }}</b><br><p>{{ facility.description }}</p><p>Responsible team: {{ facility.facility_type.responsible_team.name }} Team</p>{% if request.user.is_authenticated %}<p><a href='{{ detail }}' class='btn btn-primary' style='color: white;'><i class='fas fa-search'></i> Details</a><a href='{{ feedback }}' class='btn btn-primary' style='color: white;'><i class='fas fa-comment-dots'></i> Feedback</a></p>{% endif %}").addTo(this);
markers.push(marker.getLatLng());
{% endfor %}
{% endfor %}
var markerBounds = L.latLngBounds(markers);
// fitBounds appears to not respect maxZoom from the tilelayer,
// leading to the "Error: Attempted to load an infinite number of tiles" mess,
// so hardcode maxZoom to 13 here
this.fitBounds(markerBounds, {maxZoom: 13});
};
</script>
<script src="{% static 'js/kfmap.js' %}" type="text/javascript"></script>
{% endblock %}

0
src/maps/__init__.py Normal file
View File

5
src/maps/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class MapsConfig(AppConfig):
name = "maps"

View File

9
src/maps/urls.py Normal file
View File

@ -0,0 +1,9 @@
from django.urls import re_path
from .views import MapProxyView
app_name = "maps"
urlpatterns = [
re_path("kfproxy/(?P<path>.*)", MapProxyView.as_view(), name="proxy"),
]

19
src/maps/utils.py Normal file
View File

@ -0,0 +1,19 @@
from django.db import models
class LeafletMarkerChoices(models.TextChoices):
"""
Leaflet icon color choices, a models.TextChoices class to use when we want to set
choices for a model field to pick a marker colour for a Leaflet map.
These map directly to the L.Icon() objects in static_src/js/leaflet-color-markers.js.
"""
BLUE = "blueIcon", "Blue (#2A81CB)"
GOLD = "goldIcon", "Gold (#FFD326)"
RED = "redIcon", "Red (#CB2B3E)"
GREEN = "greenIcon", "Green (#2AAD27)"
ORANGE = "orangeIcon", "Orange (#CB8427)"
YELLOW = "yellowIcon", "Yellow (#CAC428)"
VIOLET = "violetIcon", "Violet (#9C2BCB)"
GREY = "greyIcon", "Grey (#7B7B7B)"
BLACK = "blackIcon", "Black (#3D3D3D)"

54
src/maps/views.py Normal file
View File

@ -0,0 +1,54 @@
import requests
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse
from django.views.generic import View
class MapProxyView(View):
"""
Proxy for KortForsyningen map service. Created so we can show maps without
leaking the IP of our visitors.
"""
def get(self, *args, **kwargs):
"""
Before we make the request we check that the path is in our whitelist.
Before we return the response we copy headers except for a list we dont want.
"""
# only permit certain paths
path = self.request.path.replace("/maps/kfproxy", "", 1)
if path not in ["/orto_foraar", "/topo_skaermkort", "/mat", "/dhm"]:
raise PermissionDenied("No thanks")
# ok, get the full path including querystring, and add our token
fullpath = self.request.get_full_path().replace("/maps/kfproxy", "", 1)
fullpath += f"&token={settings.KORTFORSYNINGEN_TOKEN}"
# make the request
r = requests.get("https://services.kortforsyningen.dk" + fullpath)
# make the response
response = HttpResponse(r.content, status=r.status_code)
# list of headers that cause trouble when proxying
excluded_headers = [
"connection",
"content-encoding",
"content-length",
"keep-alive",
"proxy-authenticate",
"proxy-authorization",
"te",
"trailers",
"transfer-encoding",
"upgrade",
]
# proxy all headers from our upstream request to the response to our client,
# if they headers are not in our list of troublemakers
for key, value in r.headers.items():
if key.lower() not in excluded_headers:
response[key] = value
# all good, return the response
return response

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 B

101
src/static_src/js/kfmap.js Normal file
View File

@ -0,0 +1,101 @@
(function() {
// Workaround for 1px lines appearing in some browsers due to fractional transforms
// and resulting anti-aliasing.
// https://github.com/Leaflet/Leaflet/issues/3575
if (window.navigator.userAgent.indexOf('Chrome') > -1) {
var originalInitTile = L.GridLayer.prototype._initTile;
L.GridLayer.include({
_initTile: function (tile) {
originalInitTile.call(this, tile);
var tileSize = this.getTileSize();
tile.style.width = tileSize.x + 1 + 'px';
tile.style.height = tileSize.y + 1 + 'px';
}
});
}
var myAttributionText = '&copy; <a target="_blank" href="https://download.kortforsyningen.dk/content/vilk%C3%A5r-og-betingelser">Styrelsen for Dataforsyning og Effektivisering</a>';
// we need a custom crs for the map
var crs = new L.Proj.CRS('EPSG:25832', '+proj=utm +zone=32 +ellps=GRS80 +units=m +no_defs', {
resolutions: [1638.4,819.2,409.6,204.8,102.4,51.2,25.6,12.8,6.4,3.2,1.6,0.8,0.4,0.2],
origin: [120000,6500000],
bounds: L.bounds([120000, 5661139.2],[1378291.2, 6500000])
});
// Make the map object using the custom projection
var map = new L.Map('map', {
crs: crs,
center: [55.3, 9.9], // Set center location
zoom: 9, // Initial zoom level
minzoom: 0,
maxzoom: 13,
})
// Define ortophoto layer [WMTS:orto_foraar]
var ortofotowmts = L.tileLayer('/maps/kfproxy/orto_foraar?request=GetTile&version=1.0.0&service=WMTS&Layer=orto_foraar&style=default&format=image/jpeg&TileMatrixSet=View1&TileMatrix={zoom}&TileRow={y}&TileCol={x}', {
minZoom: 0,
maxZoom: 13,
attribution: myAttributionText,
crossOrigin: true,
zoom: function () {
var zoomlevel = map._animateToZoom ? map._animateToZoom : map.getZoom();
if (zoomlevel < 10)
return 'L0' + zoomlevel;
else
return 'L' + zoomlevel;
}
}).addTo(map);
// skaermkort layer [WMTS:topo_skaermkort]
var toposkaermkortwmts = L.tileLayer.wms('/maps/kfproxy/topo_skaermkort', {
layers: 'dtk_skaermkort',
format: 'image/png',
attribution: myAttributionText
});
// hillshade tile layer [WMTS:dhm]
var dhmwmts = L.tileLayer.wms('/maps/kfproxy/dhm', {
layers: 'dhm_terraen_skyggekort_overdrevet',
format: 'image/png',
attribution: myAttributionText
});
// Matrikelskel overlay [WMS:mat]
var matrikel = L.tileLayer.wms('/maps/kfproxy/mat', {
transparent: true,
layers: 'MatrikelSkel,Centroide',
format: 'image/png',
attribution: myAttributionText,
minZoom: 9
}).addTo(map); // addTo means that the layer is visible by default
// Hillshade overlay [WMS:dhm]
var hillshade = L.tileLayer.wms('/maps/kfproxy/dhm', {
transparent: true,
layers: 'dhm_terraen_skyggekort_transparent_overdrevet',
format: 'image/png',
attribution: myAttributionText,
});
// Define layer groups for layer control
var baseLayers = {
"Ortophoto Map": ortofotowmts,
"Regular Map": toposkaermkortwmts,
"Hillshade Map": dhmwmts
};
var overlays = {
"Cadastral Map Overlay": matrikel,
"Hillshade Map Overlay": hillshade
};
// Add layer control to map
L.control.layers(baseLayers, overlays).addTo(map);
// Add scale line to map, disable imperial units
L.control.scale({imperial: false}).addTo(map);
// fire our callback when ready
map.whenReady(MapReadyCallback);
})();

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,82 @@
// borrowed from https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/js/leaflet-color-markers.js
var blueIcon = new L.Icon({
iconUrl: '/static/img/leaflet/marker-icon-2x-blue.png',
shadowUrl: '/static/img/leaflet/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
var goldIcon = new L.Icon({
iconUrl: '/static/img/leaflet/marker-icon-2x-gold.png',
shadowUrl: '/static/img/leaflet/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
var redIcon = new L.Icon({
iconUrl: '/static/img/leaflet/marker-icon-2x-red.png',
shadowUrl: '/static/img/leaflet/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
var greenIcon = new L.Icon({
iconUrl: '/static/img/leaflet/marker-icon-2x-green.png',
shadowUrl: '/static/img/leaflet/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
var orangeIcon = new L.Icon({
iconUrl: '/static/img/leaflet/marker-icon-2x-orange.png',
shadowUrl: '/static/img/leaflet/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
var yellowIcon = new L.Icon({
iconUrl: '/static/img/leaflet/marker-icon-2x-yellow.png',
shadowUrl: '/static/img/leaflet/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
var violetIcon = new L.Icon({
iconUrl: '/static/img/leaflet/marker-icon-2x-violet.png',
shadowUrl: '/static/img/leaflet/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
var greyIcon = new L.Icon({
iconUrl: '/static/img/leaflet/marker-icon-2x-grey.png',
shadowUrl: '/static/img/leaflet/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
var blackIcon = new L.Icon({
iconUrl: '/static/img/leaflet/marker-icon-2x-black.png',
shadowUrl: '/static/img/leaflet/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,272 @@
(function (factory) {
var L, proj4;
if (typeof define === 'function' && define.amd) {
// AMD
define(['leaflet', 'proj4'], factory);
} else if (typeof module === 'object' && typeof module.exports === "object") {
// Node/CommonJS
L = require('leaflet');
proj4 = require('proj4');
module.exports = factory(L, proj4);
} else {
// Browser globals
if (typeof window.L === 'undefined' || typeof window.proj4 === 'undefined')
throw 'Leaflet and proj4 must be loaded first';
factory(window.L, window.proj4);
}
}(function (L, proj4) {
if (proj4.__esModule && proj4.default) {
// If proj4 was bundled as an ES6 module, unwrap it to get
// to the actual main proj4 object.
// See discussion in https://github.com/kartena/Proj4Leaflet/pull/147
proj4 = proj4.default;
}
L.Proj = {};
L.Proj._isProj4Obj = function(a) {
return (typeof a.inverse !== 'undefined' &&
typeof a.forward !== 'undefined');
};
L.Proj.Projection = L.Class.extend({
initialize: function(code, def, bounds) {
var isP4 = L.Proj._isProj4Obj(code);
this._proj = isP4 ? code : this._projFromCodeDef(code, def);
this.bounds = isP4 ? def : bounds;
},
project: function (latlng) {
var point = this._proj.forward([latlng.lng, latlng.lat]);
return new L.Point(point[0], point[1]);
},
unproject: function (point, unbounded) {
var point2 = this._proj.inverse([point.x, point.y]);
return new L.LatLng(point2[1], point2[0], unbounded);
},
_projFromCodeDef: function(code, def) {
if (def) {
proj4.defs(code, def);
} else if (proj4.defs[code] === undefined) {
var urn = code.split(':');
if (urn.length > 3) {
code = urn[urn.length - 3] + ':' + urn[urn.length - 1];
}
if (proj4.defs[code] === undefined) {
throw 'No projection definition for code ' + code;
}
}
return proj4(code);
}
});
L.Proj.CRS = L.Class.extend({
includes: L.CRS,
options: {
transformation: new L.Transformation(1, 0, -1, 0)
},
initialize: function(a, b, c) {
var code,
proj,
def,
options;
if (L.Proj._isProj4Obj(a)) {
proj = a;
code = proj.srsCode;
options = b || {};
this.projection = new L.Proj.Projection(proj, options.bounds);
} else {
code = a;
def = b;
options = c || {};
this.projection = new L.Proj.Projection(code, def, options.bounds);
}
L.Util.setOptions(this, options);
this.code = code;
this.transformation = this.options.transformation;
if (this.options.origin) {
this.transformation =
new L.Transformation(1, -this.options.origin[0],
-1, this.options.origin[1]);
}
if (this.options.scales) {
this._scales = this.options.scales;
} else if (this.options.resolutions) {
this._scales = [];
for (var i = this.options.resolutions.length - 1; i >= 0; i--) {
if (this.options.resolutions[i]) {
this._scales[i] = 1 / this.options.resolutions[i];
}
}
}
this.infinite = !this.options.bounds;
},
scale: function(zoom) {
var iZoom = Math.floor(zoom),
baseScale,
nextScale,
scaleDiff,
zDiff;
if (zoom === iZoom) {
return this._scales[zoom];
} else {
// Non-integer zoom, interpolate
baseScale = this._scales[iZoom];
nextScale = this._scales[iZoom + 1];
scaleDiff = nextScale - baseScale;
zDiff = (zoom - iZoom);
return baseScale + scaleDiff * zDiff;
}
},
zoom: function(scale) {
// Find closest number in this._scales, down
var downScale = this._closestElement(this._scales, scale),
downZoom = this._scales.indexOf(downScale),
nextScale,
nextZoom,
scaleDiff;
// Check if scale is downScale => return array index
if (scale === downScale) {
return downZoom;
}
if (downScale === undefined) {
return -Infinity;
}
// Interpolate
nextZoom = downZoom + 1;
nextScale = this._scales[nextZoom];
if (nextScale === undefined) {
return Infinity;
}
scaleDiff = nextScale - downScale;
return (scale - downScale) / scaleDiff + downZoom;
},
distance: L.CRS.Earth.distance,
R: L.CRS.Earth.R,
/* Get the closest lowest element in an array */
_closestElement: function(array, element) {
var low;
for (var i = array.length; i--;) {
if (array[i] <= element && (low === undefined || low < array[i])) {
low = array[i];
}
}
return low;
}
});
L.Proj.GeoJSON = L.GeoJSON.extend({
initialize: function(geojson, options) {
this._callLevel = 0;
L.GeoJSON.prototype.initialize.call(this, geojson, options);
},
addData: function(geojson) {
var crs;
if (geojson) {
if (geojson.crs && geojson.crs.type === 'name') {
crs = new L.Proj.CRS(geojson.crs.properties.name);
} else if (geojson.crs && geojson.crs.type) {
crs = new L.Proj.CRS(geojson.crs.type + ':' + geojson.crs.properties.code);
}
if (crs !== undefined) {
this.options.coordsToLatLng = function(coords) {
var point = L.point(coords[0], coords[1]);
return crs.projection.unproject(point);
};
}
}
// Base class' addData might call us recursively, but
// CRS shouldn't be cleared in that case, since CRS applies
// to the whole GeoJSON, inluding sub-features.
this._callLevel++;
try {
L.GeoJSON.prototype.addData.call(this, geojson);
} finally {
this._callLevel--;
if (this._callLevel === 0) {
delete this.options.coordsToLatLng;
}
}
}
});
L.Proj.geoJson = function(geojson, options) {
return new L.Proj.GeoJSON(geojson, options);
};
L.Proj.ImageOverlay = L.ImageOverlay.extend({
initialize: function (url, bounds, options) {
L.ImageOverlay.prototype.initialize.call(this, url, null, options);
this._projectedBounds = bounds;
},
// Danger ahead: Overriding internal methods in Leaflet.
// Decided to do this rather than making a copy of L.ImageOverlay
// and doing very tiny modifications to it.
// Future will tell if this was wise or not.
_animateZoom: function (event) {
var scale = this._map.getZoomScale(event.zoom);
var northWest = L.point(this._projectedBounds.min.x, this._projectedBounds.max.y);
var offset = this._projectedToNewLayerPoint(northWest, event.zoom, event.center);
L.DomUtil.setTransform(this._image, offset, scale);
},
_reset: function () {
var zoom = this._map.getZoom();
var pixelOrigin = this._map.getPixelOrigin();
var bounds = L.bounds(
this._transform(this._projectedBounds.min, zoom)._subtract(pixelOrigin),
this._transform(this._projectedBounds.max, zoom)._subtract(pixelOrigin)
);
var size = bounds.getSize();
L.DomUtil.setPosition(this._image, bounds.min);
this._image.style.width = size.x + 'px';
this._image.style.height = size.y + 'px';
},
_projectedToNewLayerPoint: function (point, zoom, center) {
var viewHalf = this._map.getSize()._divideBy(2);
var newTopLeft = this._map.project(center, zoom)._subtract(viewHalf)._round();
var topLeft = newTopLeft.add(this._map._getMapPanePos());
return this._transform(point, zoom)._subtract(topLeft);
},
_transform: function (point, zoom) {
var crs = this._map.options.crs;
var transformation = crs.transformation;
var scale = crs.scale(zoom);
return transformation.transform(point, scale);
}
});
L.Proj.imageOverlay = function (url, bounds, options) {
return new L.Proj.ImageOverlay(url, bounds, options);
};
return L.Proj;
}));

View File

@ -203,6 +203,7 @@ class Command(BaseCommand):
name="Toilets",
description="All the toilets",
icon="fas fa-toilet",
marker="greyIcon",
responsible_team=teams["shit"],
)
types["toilet"].quickfeedback_options.add(options["na"])
@ -214,6 +215,7 @@ class Command(BaseCommand):
name="Power Infrastructure",
description="Power related infrastructure, distribution points, distribution cables, and so on.",
icon="fas fa-plug",
marker="goldIcon",
responsible_team=teams["power"],
)
types["power"].quickfeedback_options.add(options["attention"])
@ -225,39 +227,39 @@ class Command(BaseCommand):
self.output("Creating facilities...")
facilities["toilet1"] = Facility.objects.create(
facility_type=facility_types["toilet"],
name="Toilet A1",
description="Toilet on the left side in the NOC building",
location=Point(1, 2),
name="Toilet NOC East",
description="Toilet on the east side of the NOC building",
location=Point(9.939783, 55.387217),
)
facilities["toilet2"] = Facility.objects.create(
facility_type=facility_types["toilet"],
name="Toilet A2",
description="Toilet on the right side in the NOC building",
location=Point(3, 4),
name="Toilet NOC West",
description="Toilet on the west side of the NOC building",
location=Point(9.93967, 55.387197),
)
facilities["pdp1"] = Facility.objects.create(
facility_type=facility_types["power"],
name="PDP1",
description="In orga area",
location=Point(5, 6),
location=Point(9.94079, 55.388022),
)
facilities["pdp2"] = Facility.objects.create(
facility_type=facility_types["power"],
name="PDP2",
description="In bar area",
location=Point(7, 8),
location=Point(9.942036, 55.387891),
)
facilities["pdp3"] = Facility.objects.create(
facility_type=facility_types["power"],
name="PDP3",
description="In speaker tent",
location=Point(9, 10),
location=Point(9.938416, 55.387109),
)
facilities["pdp4"] = Facility.objects.create(
facility_type=facility_types["power"],
name="PDP4",
description="In food area",
location=Point(11, 12),
location=Point(9.940146, 55.386983),
)
return facilities