From dcebcc6b1b5b03f7fd37864ded04fabdba0a0b57 Mon Sep 17 00:00:00 2001 From: Jonas Smedegaard Date: Thu, 4 Jan 2018 22:27:35 +0100 Subject: Initial draft. --- .gitignore | 3 ++ .perltidyrc | 23 +++++++++ .tidyallrc | 18 +++++++ Makefile | 40 +++++++++++++++ README.md | 51 +++++++++++++++++++ USE.md | 78 ++++++++++++++++++++++++++++ bin/build.js | 41 +++++++++++++++ bin/build.psgi | 21 ++++++++ bin/src.psgi | 20 ++++++++ src/css/leaflet/MarkerCluster.Default.css | 1 + src/css/leaflet/MarkerCluster.css | 1 + src/css/leaflet/leaflet.css | 1 + src/css/map.css | 23 +++++++++ src/data/staff.json | 20 ++++++++ src/img/leaflet | 1 + src/js/app/mapfactory.js | 26 ++++++++++ src/js/app/places.js | 16 ++++++ src/js/app/position.js | 21 ++++++++ src/js/lib/leaflet.js | 1 + src/js/lib/leaflet.markercluster.js | 1 + src/js/lib/require.js | 1 + src/js/lib/require/json.js | 84 +++++++++++++++++++++++++++++++ src/js/lib/require/text.js | 1 + src/js/slippymap.js | 13 +++++ src/js/world-staff.js | 18 +++++++ src/world/staff/index.html | 15 ++++++ 26 files changed, 539 insertions(+) create mode 100644 .gitignore create mode 100644 .perltidyrc create mode 100644 .tidyallrc create mode 100644 Makefile create mode 100644 README.md create mode 100644 USE.md create mode 100644 bin/build.js create mode 100755 bin/build.psgi create mode 100755 bin/src.psgi create mode 120000 src/css/leaflet/MarkerCluster.Default.css create mode 120000 src/css/leaflet/MarkerCluster.css create mode 120000 src/css/leaflet/leaflet.css create mode 100644 src/css/map.css create mode 100644 src/data/staff.json create mode 120000 src/img/leaflet create mode 100644 src/js/app/mapfactory.js create mode 100644 src/js/app/places.js create mode 100644 src/js/app/position.js create mode 120000 src/js/lib/leaflet.js create mode 120000 src/js/lib/leaflet.markercluster.js create mode 120000 src/js/lib/require.js create mode 100644 src/js/lib/require/json.js create mode 120000 src/js/lib/require/text.js create mode 100644 src/js/slippymap.js create mode 100644 src/js/world-staff.js create mode 100644 src/world/staff/index.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab3f338 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.tidyall.d/ +build/ +/build-* diff --git a/.perltidyrc b/.perltidyrc new file mode 100644 index 0000000..d50e6d7 --- /dev/null +++ b/.perltidyrc @@ -0,0 +1,23 @@ +# Settings for perltidy + +# use best practices, except use of stdout +--perl-best-practices +--no-standard-output +--no-standard-error-output + +# use TAB for lead indentation +--tabs +--entab-leading-whitespace=4 +-nola + +# indent only already indented comments +--indent-spaced-block-comments + +# put brace on new line for named subroutines +--opening-sub-brace-on-new-line + +# preserve horisontally styled lists +--break-at-old-comma-breakpoints + +# overwrite (we use CVS), and leave backup only on error +--backup-file-extension=/~ diff --git a/.tidyallrc b/.tidyallrc new file mode 100644 index 0000000..bc3a0cc --- /dev/null +++ b/.tidyallrc @@ -0,0 +1,18 @@ +;; Settings for tidyall +;: Usage: tidyall -a + +[PerlTidy] +select = **/*.{pl,pm,t,psgi} +argv = --profile=$ROOT/.perltidyrc + +;; TODO: write/extend plugin to support TAB +;; workaround: jq --tab --sort-keys . < $file | sponge $file +;; workaround: jq --tab --sort-keys '.features|=sort_by(.geometry.type)|.features|=sort_by(.properties.name)' src/data/staff.json | sponge src/data/staff.json +;[JSON] +;select = src/data/**/*.json + +;; TODO: package plugin +;; workaround: js-beautify --end-with-newline --indent-with-tabs --replace $file +;[JSBeautify] +;select = src/js/**/*.js bin/*.js +;ignore = src/js/lib/* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0dde3e9 --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +pkg-plack-core = libplack-perl +# TODO: Extend when Plack::Middleware::IndexDir is in stable Debian +pkg-plack-extra = libplack-middleware-deflater-perl +# TODO: Use Leaflet.Markercluster only optionally +pkg-js = \ + libjs-requirejs libjs-requirejs-text \ + libjs-leaflet libjs-leaflet-markercluster +pkg-nodejs = node-requirejs +pkg-code-minimal = $(pkg-plack-core) $(pkg-js) +pkg-code-quick = $(pkg-code-minimal) $(pkg-plack-extra) +pkg-code = $(pkg-code-quick) $(pkg-nodejs) jq +lists = $(patsubst %,list-%,\ + pkg-code pkg-code-quick pkg-code-minimal) + +all: build-compact + +# TODO: Call node (not nodejs) when Nodejs 6 (Debian Buster) is commonly used +build-compact: + nodejs /usr/lib/nodejs/requirejs/r.js -o bin/build.js + jq --tab --sort-keys -c '.features|=sort_by(.geometry.type)|.features|=sort_by(.properties.name)' \ + < build/data/staff.json \ + > build/data/staff.json~ + mv -f build/data/staff.json~ build/data/staff.json + touch $@ + +# load httpd service +serve-quick: + bin/src.psgi +serve-compact: + bin/build.psgi + +# machine-readable output (e.g. APT package dependencies) +$(lists): list-%: + @echo "$(sort $($*))" + +clean: + rm -rf build + rm -f build-compact + +.PHONY: all clean list-% serve-% diff --git a/README.md b/README.md new file mode 100644 index 0000000..ff6d93a --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# Maps + +This project produces a set of slippy maps. + + +## Overview + +This document provides a broad overview of the project. + +For detailed information, +please consult these additional documents: + + * - ways to use of the project as-is, and their requirements + * - future tasks and ideas + * - copyright and licensing info + +Also, each source file - +in particular the scripts below - +contain specific notes on use and requirements at the top of the file. + + +## Usage + +When needed software is installed +a web service can be started +and the visualizations accessed from a web browser. + +A minimal web service is included +ready to use for quick preview. +For large-scale publication +a production quality service like Apache Httpd is recommended. + +See file for detailed information +on ways to use of the project as-is, and their requirements. + + +## Rights + +See file for detailed copyright and licensing info. + +Please do not hesitate to ask if in doubt. + + +## Contact + +Main developer: Jonas Smedegaard . + +We would love to hear from you, +whether about uses and reuses of the project, +interest in helping out to improve some things, +or just excitement or scepticism, or whatever :-) diff --git a/USE.md b/USE.md new file mode 100644 index 0000000..81103d6 --- /dev/null +++ b/USE.md @@ -0,0 +1,78 @@ +# Using these slippy maps + +This project is usable in several ways: + + * Quick - faster to prepare but loads slower into web browser + * Compact - compiled for fast and compact hosting + +For both approaches the code must be served via a web server +(loading from filesystem directly into web browser does not work). + + +## Quick + +Quick serving is handy during development, +as changes can be tested without building everything. + + +### Prerequisites + +Show needed packages, +and install them with APT (i.e. as root): + + make list-pkg-code-quick + sudo apt install $ABOVE_PACKAGE_LIST + +(or install pkglist-code-minimal for a slightly rough visual result) + + +### Serve + +Start a tiny webserver, +and open a web browser at the local address it tells you: + + make serve-quick + + +## Compact + +Compact serving is optimized for production use, +e.g. on a public host served with Apache. + +Code and data gets compiled together, +optimized for the smallest possible size and complexity +for each single concrete map. + + +### Prerequisites + +Show needed packages, +and install them with APT (i.e. as root): + + make list-pkg-code + sudo apt install $ABOVE_PACKAGE_LIST + + +### Build + +To build functional site but skip image fetching: + + make build-compact + +To build everything, including image fetching: + + make + + +### Serve + +Start a tiny webserver, +and open a web browser at the local address it tells you: + + make serve-quick + +For production use, +consider to instead setup e.g. Apache to serve directory. +The directory is fully self-contained +(contain no symlinks to or html links to system-wide code) +so can be copied to an external web hosting provider. diff --git a/bin/build.js b/bin/build.js new file mode 100644 index 0000000..3b750f5 --- /dev/null +++ b/bin/build.js @@ -0,0 +1,41 @@ +{ + appDir: '../src', + mainConfigFile: '../src/js/slippymap.js', + dir: '../build', + modules: [ + //First set up the common build layer. + { + //module names are relative to baseUrl + name: '../slippymap', + //List common dependencies here. Only need to list + //top level dependencies, "include" will find + //nested dependencies. + include: [ + 'leaflet', + 'app/mapfactory', + 'app/places', + 'app/position' + ] + }, + + //Now set up a build layer for each page, but exclude + //the common one. "exclude" will exclude + //the nested, built dependencies from "slippymap". Any + //"exclude" that includes built modules should be + //listed before the build layer that wants to exclude it. + //"include" the appropriate "app/main*" module since by default + //it will not get added to the build since it is loaded by a nested + //requirejs in the page*.js files. + { + name: '../world-staff', + include: [ + 'json!data/staff.json' + ], + exclude: ['../slippymap'] + } + + ], + optimize: "uglify2", + optimizeCss: "standard.keepLines", + removeCombined: true, +} diff --git a/bin/build.psgi b/bin/build.psgi new file mode 100755 index 0000000..cdc51ef --- /dev/null +++ b/bin/build.psgi @@ -0,0 +1,21 @@ +#!/usr/bin/env plackup + +use strict; +use warnings; + +use FindBin qw($Bin); + +use Plack::Builder; + +#use Plack::App::File; +use Plack::App::Directory; + +builder { + eval { enable 'DirIndex' }; + eval { enable 'Deflater' }; + +# enable 'Debug', panels => [ qw(DBITrace Memory Timer) ]; +# mount '/usr/share/javascript' => Plack::App::File->new( root => '/usr/share/javascript' )->to_app; + mount '/' => + Plack::App::Directory->new( root => "$Bin/../build" )->to_app; +}; diff --git a/bin/src.psgi b/bin/src.psgi new file mode 100755 index 0000000..44e557a --- /dev/null +++ b/bin/src.psgi @@ -0,0 +1,20 @@ +#!/usr/bin/env plackup + +use strict; +use warnings; + +use FindBin qw($Bin); + +use Plack::Builder; + +#use Plack::App::File; +use Plack::App::Directory; + +builder { + eval { enable 'DirIndex' }; + eval { enable 'Deflater' }; + +# enable 'Debug', panels => [ qw(DBITrace Memory Timer) ]; +# mount '/usr/share/javascript' => Plack::App::File->new( root => '/usr/share/javascript' )->to_app; + mount '/' => Plack::App::Directory->new( root => "$Bin/../src" )->to_app; +}; diff --git a/src/css/leaflet/MarkerCluster.Default.css b/src/css/leaflet/MarkerCluster.Default.css new file mode 120000 index 0000000..2cf500e --- /dev/null +++ b/src/css/leaflet/MarkerCluster.Default.css @@ -0,0 +1 @@ +/usr/share/javascript/leaflet/MarkerCluster.Default.css \ No newline at end of file diff --git a/src/css/leaflet/MarkerCluster.css b/src/css/leaflet/MarkerCluster.css new file mode 120000 index 0000000..a791781 --- /dev/null +++ b/src/css/leaflet/MarkerCluster.css @@ -0,0 +1 @@ +/usr/share/javascript/leaflet/MarkerCluster.css \ No newline at end of file diff --git a/src/css/leaflet/leaflet.css b/src/css/leaflet/leaflet.css new file mode 120000 index 0000000..40a1f03 --- /dev/null +++ b/src/css/leaflet/leaflet.css @@ -0,0 +1 @@ +/usr/share/javascript/leaflet/leaflet.css \ No newline at end of file diff --git a/src/css/map.css b/src/css/map.css new file mode 100644 index 0000000..626ff84 --- /dev/null +++ b/src/css/map.css @@ -0,0 +1,23 @@ +.leaflet-control-layers-toggle { + background-image: url(../img/leaflet/layers.png); +} + +.leaflet-retina .leaflet-control-layers-toggle { + background-image: url(../img/leaflet/layers-2x.png); +} + +#content { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; +} + +.leaflet-control-scale { + text-align: center; +} + +.info { + background-color: white; +} diff --git a/src/data/staff.json b/src/data/staff.json new file mode 100644 index 0000000..c103523 --- /dev/null +++ b/src/data/staff.json @@ -0,0 +1,20 @@ +{ + "features": [ + { + "geometry": { + "coordinates": [ + 11.81707, + 55.76429 + ], + "type": "Point" + }, + "properties": { + "id": "jonas.smedegaard", + "name": "Jonas Smedegaard", + "title": "PureOS Developer" + }, + "type": "Feature" + } + ], + "type": "FeatureCollection" +} diff --git a/src/img/leaflet b/src/img/leaflet new file mode 120000 index 0000000..a962773 --- /dev/null +++ b/src/img/leaflet @@ -0,0 +1 @@ +/usr/share/javascript/leaflet/images \ No newline at end of file diff --git a/src/js/app/mapfactory.js b/src/js/app/mapfactory.js new file mode 100644 index 0000000..8d8bb0e --- /dev/null +++ b/src/js/app/mapfactory.js @@ -0,0 +1,26 @@ +define(['leaflet'], function(L) { + // base config + var attribOSM = '© OpenStreetMap contributors', + OSMLayer = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: attribOSM + }), + scale = L.control.scale({ + imperial: false + }); + + return function(id, bounds) { + var map = L.map(id, { + layers: [OSMLayer] + }) + if (bounds) { + map.fitBounds(L.latLngBounds(bounds)); + } else { + map.fitWorld().zoomIn(); + } + map.attributionControl.setPrefix(false); + + scale.addTo(map); + + return map; + }; +}); diff --git a/src/js/app/places.js b/src/js/app/places.js new file mode 100644 index 0000000..9b915cb --- /dev/null +++ b/src/js/app/places.js @@ -0,0 +1,16 @@ +define(['leaflet'], function(L) { + + // GeoJSON feature grouping + function returnMarker(feature, latlng) { + return L.marker(latlng); + }; + + var place = L.geoJson([], { + pointToLayer: returnMarker + }); + + return function(data) { + place.addData(data); + return L.layerGroup().addLayer(place); + }; +}); diff --git a/src/js/app/position.js b/src/js/app/position.js new file mode 100644 index 0000000..0e9bfb2 --- /dev/null +++ b/src/js/app/position.js @@ -0,0 +1,21 @@ +define(['leaflet'], function(L) { + + // position popup + function round(n,d) { + return Math.round(Math.pow(10,d)*n)/Math.pow(10,d) + }; + function lngLatString(latLng) { + return round(latLng.lng,5) + ", " + round(latLng.lat,5) + }; + var popup = L.popup(); + + return function positionHook(map) { + function positionPopup(e) { + popup + .setLatLng(e.latlng) + .setContent("Position (long, lat):
" + lngLatString(e.latlng)) + .openOn(map); + } + map.on('contextmenu', positionPopup); + } +}); diff --git a/src/js/lib/leaflet.js b/src/js/lib/leaflet.js new file mode 120000 index 0000000..f0df49d --- /dev/null +++ b/src/js/lib/leaflet.js @@ -0,0 +1 @@ +/usr/share/javascript/leaflet/leaflet.js \ No newline at end of file diff --git a/src/js/lib/leaflet.markercluster.js b/src/js/lib/leaflet.markercluster.js new file mode 120000 index 0000000..881bcea --- /dev/null +++ b/src/js/lib/leaflet.markercluster.js @@ -0,0 +1 @@ +/usr/share/javascript/leaflet/leaflet.markercluster.js \ No newline at end of file diff --git a/src/js/lib/require.js b/src/js/lib/require.js new file mode 120000 index 0000000..a2b8052 --- /dev/null +++ b/src/js/lib/require.js @@ -0,0 +1 @@ +/usr/share/javascript/requirejs/require.js \ No newline at end of file diff --git a/src/js/lib/require/json.js b/src/js/lib/require/json.js new file mode 100644 index 0000000..40e4da1 --- /dev/null +++ b/src/js/lib/require/json.js @@ -0,0 +1,84 @@ +/** @license + * RequireJS plugin for loading JSON files + * - depends on Text plugin and it was HEAVILY "inspired" by it as well. + * Author: Miller Medeiros + * Version: 0.4.0 (2014/04/10) + * Released under the MIT license + * + * Patched (2013/10/10): + * - supports JS-like comments which are beginning from /* or // + */ +define(['text'], function (text) { + + var CACHE_BUST_QUERY_PARAM = 'bust', + CACHE_BUST_FLAG = '!bust', + jsonParse = (typeof JSON !== 'undefined' && typeof JSON.parse === 'function') ? JSON.parse : function (val) { + return eval('(' + val + ')'); //quick and dirty + }, + PROTECTION_PREFIX = /^\)\]\}',?\n/, + buildMap = {}; + + function cacheBust(url) { + url = url.replace(CACHE_BUST_FLAG, ''); + url += (url.indexOf('?') < 0) ? '?' : '&'; + return url + CACHE_BUST_QUERY_PARAM + '=' + Math.round(2147483647 * Math.random()); + } + + //API + return { + load: function(name, req, onLoad, config) { + // Make sure file part of url ends with .json, add it if not + name = name.replace(new RegExp("^[^?]*"), function(base) { + return base.substr(-5) === ".json" ? base : base + ".json"; + }); + var url = req.toUrl(name); + if (config.isBuild && (config.inlineJSON === false || name.indexOf(CACHE_BUST_QUERY_PARAM + '=') !== -1)) { + //avoid inlining cache busted JSON or if inlineJSON:false + onLoad(null); + } else if (url.indexOf('empty:') === 0) { + //and don't inline files marked as empty: urls + onLoad(null); + } else { + text.get(url, + function (data) { + // Need to check if the JSON data has been formatted for the JSON array security vulnerability + var cleaned_data = ('' + data).replace(PROTECTION_PREFIX, ''); + cleaned_data = cleaned_data.replace(/\/\*.+?\*\/|\/\/[^\n\r]*/g, ''); + var parsed = null; + try { + parsed = jsonParse(cleaned_data); + if (config.isBuild) { + buildMap[name] = parsed; + } + onLoad(parsed); + } catch (e) { + onLoad.error(e); + //onLoad(null); -- should we really call onLoad??? + } + }, + onLoad.error, { + accept: 'application/json' + } + ); + } + }, + + normalize: function (name, normalize) { + // used normalize to avoid caching references to a "cache busted" request + if (name.indexOf(CACHE_BUST_FLAG) !== -1) { + name = cacheBust(name); + } + // resolve any relative paths + return normalize(name); + }, + + // write method based on RequireJS official text plugin by James Burke + // https://github.com/jrburke/requirejs/blob/master/text.js + write: function (pluginName, moduleName, write) { + if (moduleName in buildMap) { + var content = buildMap[moduleName]; + write('define("' + pluginName + '!' + moduleName + '", function () { return ' + (content ? JSON.stringify(content) : content) + '; });\n'); + } + } + }; +}); diff --git a/src/js/lib/require/text.js b/src/js/lib/require/text.js new file mode 120000 index 0000000..2b490b4 --- /dev/null +++ b/src/js/lib/require/text.js @@ -0,0 +1 @@ +/usr/share/javascript/requirejs/text.js \ No newline at end of file diff --git a/src/js/slippymap.js b/src/js/slippymap.js new file mode 100644 index 0000000..ac76efd --- /dev/null +++ b/src/js/slippymap.js @@ -0,0 +1,13 @@ +// shared code common across pages +requirejs.config({ + baseUrl: 'js/lib', + paths: { + text: 'require/text', + json: 'require/json', + app: '../app', + data: '../../data' + }, + shim: { + 'leaflet.markercluster': ['leaflet'] + } +}); diff --git a/src/js/world-staff.js b/src/js/world-staff.js new file mode 100644 index 0000000..618c2fd --- /dev/null +++ b/src/js/world-staff.js @@ -0,0 +1,18 @@ +//Load common code that includes config, then load the app logic for this page. +requirejs(['./slippymap'], function(_foo) { + requirejs.config({ + baseUrl: '../../js/lib', + }); + L.Icon.Default.imagePath = '../../img/leaflet'; + requirejs(['app/mapfactory'], function(mkmap) { + var map = mkmap('content'); + requirejs([ + 'app/places', + 'json!data/staff.json', + 'app/position' + ], function(places, data, hook) { + map.addLayer(places(data)); + hook(map); + }); + }); +}); diff --git a/src/world/staff/index.html b/src/world/staff/index.html new file mode 100644 index 0000000..4782775 --- /dev/null +++ b/src/world/staff/index.html @@ -0,0 +1,15 @@ + + + + +Staff, worldwide + + + + + + +
+ + + -- cgit v1.2.3