Initial version of subway render with leaflet js library

This commit is contained in:
Alexey Zakharenkov 2019-06-18 19:22:26 +03:00
parent 56f9a9586c
commit 3b6655f259
5 changed files with 431 additions and 0 deletions

2
.gitignore vendored
View file

@ -1,6 +1,8 @@
__pycache__/
tmp_html/
html/
.idea
.DS_Store
*.log
*.json
*.geojson

View file

@ -0,0 +1,6 @@
Abuja, Nigeria
Addis Ababa, Ethiopia
Berlin S-Bahn, Germany
Moscow, Russia
Zhengzhou, China
İzmir, Turkey

250
render/js/lib/xhr.js Normal file
View file

@ -0,0 +1,250 @@
/**
* XHR.js - A vanilla javascript wrapper for the XMLHttpRequest object.
* @version 0.1.1
* @author George Raptis (https://github.com/georapbox)
*
* The MIT License (MIT)
*
* Copyright (c) 2014 George Raptis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
(function (name, context, definition) {
if (typeof module !== 'undefined' && module.exports) {
module.exports = definition();
} else if (typeof define === 'function' && define.amd) {
define(definition);
} else {
context[name] = definition();
}
}('XHR', this, function () {
var helpers = {
extend: function() {
'use strict';
for (var i = 1, l = arguments.length; i < l; i += 1) {
for (var key in arguments[i]) {
if (arguments[i].hasOwnProperty(key)) {
if (arguments[i][key] && arguments[i][key].constructor && arguments[i][key].constructor === Object) {
arguments[0][key] = arguments[0][key] || {};
helpers.extend(arguments[0][key], arguments[i][key]);
} else {
arguments[0][key] = arguments[i][key];
}
}
}
}
return arguments[0];
},
encodeUrl: function (url) {
var domain = url.substring(0, url.indexOf('?') + 1),
search = url.substring(url.indexOf('?') + 1),
vars = search ? search.split('&') : [],
varsLen = vars.length,
encodedUrl = domain,
pair,
i;
for (i = 0; i < varsLen; i += 1) {
pair = vars[i].split('=');
encodedUrl += encodeURIComponent(pair[0]) + '=' + encodeURIComponent(pair[1]) + '&';
}
encodedUrl = encodedUrl.substring(0, encodedUrl.length - 1);
return encodedUrl;
},
serialize: function (form) {
var parts = [],
field = null,
i,
len,
j,
optLen,
option,
optValue;
for (i = 0, len = form.elements.length; i < len; i += 1) {
field = form.elements[i];
switch (field.type) {
case 'select-one':
case 'select-multiple':
if (field.name.length) {
for (j = 0, optLen = field.options.length; j < optLen; j += 1) {
option = field.options[j];
if (option.selected) {
optValue = '';
if (option.hasAttribute) {
optValue = (option.hasAttribute('value') ? option.value : option.text);
} else {
optValue = (option.attributes.value.specified ? option.value : option.text);
}
parts.push(encodeURIComponent(field.name) + '=' + encodeURIComponent(optValue));
}
}
}
break;
case undefined: // fieldset
case 'file': // file input
case 'submit': // submit button
case 'reset': // reset button
case 'button': // custom button
break;
case 'radio': // radio button
case 'checkbox': // checkbox
if (!field.checked) {
break;
}
/* falls through */
default:
// Don't include form fields without names.
if (field.name.length) {
parts.push(encodeURIComponent(field.name) + '=' + encodeURIComponent(field.value));
}
}
}
return parts.join('&');
}
};
var XHR = function (options) {
var that = this,
defaults,
xhr,
i,
customHeadersLen,
customHeadersItem;
// Define default options.
defaults = {
method: 'get', // Type of request.
url: '', // Request url (relative path).
async: true, // Defines if request is asynchronous or not.
serialize: false, // Defines if forms data sent in a POST request should be serialized.
data: null, // Data to be sent as the body of the request. Default is "null" for browser compatibility issues.
contentType: 'application/x-www-form-urlencoded', // Sets the Content Type of the request.
responseType: 'xml',
customHeaders: [], // Set custom request headers. Default value is empty array.
success: function () {}, // Callback function to handle success.
error: function () {} // Callback function to handle errors.
};
// Extend the default options with user's specified ones.
options = helpers.extend({}, defaults, options);
that.method = options.method;
that.url = options.url;
that.async = options.async;
that.serialize = options.serialize;
that.data = options.data;
that.contentType = options.contentType;
that.responseType = options.responseType;
that.customHeaders = options.customHeaders;
that.success = options.success;
that.error = options.error;
that.progressEvent = {};
customHeadersLen = that.customHeaders.length;
// Create a new XMLHttpRequest.
xhr = new XMLHttpRequest();
// onreadystatechange event
xhr.onreadystatechange = function () {
// if request is completed, handle success or error states.
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
that.success(xhr.status, xhr.responseText, xhr.responseXML, xhr.statusText);
} else {
that.error(xhr.status, xhr.responseText, xhr.responseXML, xhr.statusText);
}
}
};
// onprogress event
xhr.onprogress = function (event) {
if (event.lengthComputable){
that.progressEvent = {
bubbles: event.bubbles,
cancelable: event.cancelable,
currentTarget: event.currentTarget,
defaultPrevented: event.defaultPrevented,
eventPhase: event.eventPhase,
explicitOriginalTarget: event.explicitOriginalTarget,
isTrusted: event.isTrusted,
lengthComputable: event.lengthComputable,
loaded: event.loaded,
originalTarget: event.originalTarget,
target: event.target,
timeStamp: event.timeStamp,
total: event.total,
type: event.type
};
return that.progressEvent;
}
};
// Encode URL in case of a "GET" request.
if (that.method === 'get') {
that.url = helpers.encodeUrl(that.url);
}
// Prepare the request to be sent.
xhr.open(that.method, that.url, that.async);
// Set "Content-Type".
if (that.contentType !== false) {
xhr.setRequestHeader('Content-Type', that.contentType);
}
// Set custom headers.
if (customHeadersLen > 0) {
for (i = 0; i < customHeadersLen; i += 1) {
customHeadersItem = that.customHeaders[i];
if (typeof customHeadersItem === 'object') {
for (var prop in customHeadersItem) {
if (customHeadersItem.hasOwnProperty(prop)) {
xhr.setRequestHeader(prop, customHeadersItem[prop]);
}
}
} else {
throw new Error('Property "customHeader" expects an array of objects for value.');
}
}
}
// Serialize form if option set to "true".
if (that.serialize === true) {
that.data = helpers.serialize(that.data);
}
// Send data.
xhr.send(that.data);
return that;
};
return XHR;
}));

121
render/js/metro.js Normal file
View file

@ -0,0 +1,121 @@
function slugify(name) {
name = name.toLowerCase();
name = name.replace(/ /g, '_');
name = name.replace(/[^a-z0-9_-]+/g, '');
return name;
}
// Inspired by http://ahalota.github.io/Leaflet.CountrySelect
L.CitySelect = L.Control.extend({
options: {
position: 'topright',
title: 'City'
},
onAdd: function(map) {
this.div = L.DomUtil.create('div');
this.select = L.DomUtil.create('select', 'leaflet-countryselect', this.div);
var that = this;
var xhr = new XHR({
method: 'get',
url: 'cities.txt?',
async: true,
data: {},
serialize: false,
success: function (status, responseText, responseXML, statusText) {
if (status == 200 && responseText) {
var cities = responseText.split("\n");
cities = cities.filter(city => city.length).sort();
var content = '';
if (that.options.title.length > 0) {
content += '<option>' + that.options.title + '</option>';
}
for (var i = 0; i < cities.length; ++i){
city_name = cities[i].split(',')[0];
content += '<option value="' + city_name+ '">' + cities[i] + '</option>';
}
that.select.innerHTML = content;
}
},
error: function (status, responseText, responseXML, statusText) {
console.log('Request was unsuccessful: ' + status + ', ' + statusText);
}
});
this.select.onmousedown = L.DomEvent.stopPropagation;
return this.div;
},
on: function(type, handler) {
if (type == 'change') {
this.onChange = handler;
L.DomEvent.addListener(this.select, 'change', this._onChange, this);
} else {
console.log('CitySelect - cannot handle ' + type + ' events.')
}
},
_onChange: function(e) {
var selectedCity = this.select.options[this.select.selectedIndex].value;
e.cityName = selectedCity;
this.onChange(e);
}
});
L.citySelect = function(id, options) {
return new L.CitySelect(id, options);
};
var selector = L.citySelect({position: 'topright'}).addTo(map);
selector.on('change', function(e) {
if (e.cityName === 'City')
return;
var cityName = slugify(e.cityName);
var xhr = new XHR({
method: 'get',
url: cityName + '.geojson?',
async: true,
responseType: 'json',
data: {},
serialize: false,
success: function (status, responseText, responseXML, statusText) {
if (status == 200 && responseText) {
var json = JSON.parse(responseText);
var newCity = L.geoJSON(json, {
style: function(feature) {
if ('stroke' in feature.properties)
return {color: feature.properties.stroke};
},
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {
color: feature.properties['marker-color'],
//line-width: 1,
//weight: 1,
radius: 4
});
}
});
if (map.previousCity != null) {
map.removeLayer(map.previousCity);
}
map.previousCity = newCity;
map.addLayer(newCity);
map.fitBounds(newCity.getBounds());
}
},
error: function (status, responseText, responseXML, statusText) {
console.log('Request was unsuccessful: ' + status + ', ' + statusText);
}
});
});

52
render/render.html Normal file
View file

@ -0,0 +1,52 @@
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.5.1/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.5.1/dist/leaflet.js" integrity="sha512-GffPMF3RvMeYyc1LWMHtK8EbPv0iNZ8/oTtHPx9/cc2ILxQ+u905qIwdpULaqDkyBKgOaB57QTMg7ztg8Jm2Og==" crossorigin=""></script>
<style>
html, body
{
height: 100%;
min-height: 100%;
margin: 0;
padding: 0;
}
#map {
height: 100%;
min-height: 100%;
}
.leaflet-countryselect {
width: 200px;
}
</style>
<script>
OSM_URL = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
</script>
</head>
<body>
<div id="map"></div>
<script>
var osmAttrib = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
osm = L.tileLayer(OSM_URL, {
maxZoom: 18,
attribution: osmAttrib,
opacity: 0.5
});
var initialLocation = [55.7510888, 37.7642849];
var map = L.map('map').setView(initialLocation, 15).addLayer(osm);
L.marker(initialLocation)
.addTo(map)
.bindPopup('Choose a city from the list at the top-right corner!')
.openPopup();
</script>
<script src="js/lib/xhr.js"></script>
<script src="js/metro.js"></script>
</body>
</html>