Skip to content

Commit

Permalink
Refined the curvature overlay with automatic updating and better styling
Browse files Browse the repository at this point in the history
  • Loading branch information
Sp3EdeR committed Jan 10, 2024
1 parent d54de60 commit 2e6c153
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 943 deletions.
10 changes: 9 additions & 1 deletion .github/actions/build-website/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,12 @@ runs:
sudo pip install rjsmin==1.2.1
sudo pip install rcssmin==1.1.1
sudo pip install lxml==4.9.2
python ".github/scripts/optimize-site.py" .
python ".github/scripts/optimize-site.py" .
- name: Download curves data
# This step downloads and converts autogenerated curvature data for a map overlay.
# The script supports processing KMZ files provided by kml.roadcurvature.com. "Curve" KMZ format is not supported.
shell: bash
run: |
sudo pip install --upgrade pip
sudo pip install kml2geojson==5.1.0
python ".github/scripts/get-curve-map.py" "https://kml.roadcurvature.com/europe/hungary.c_1000.kmz" "map/curves.geojson" "map/curves-style.json"
67 changes: 67 additions & 0 deletions .github/scripts/get-curve-map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import argparse
import colorsys
import io
import json
import kml2geojson.main as k2g
import os
import urllib.request as req
import zipfile

def main():
parser = argparse.ArgumentParser()
parser.add_argument('kmz_url', help='The URL of the KML file that is to be downloaded.')
parser.add_argument('geojson_path', help='The path of the created geojson file.')
parser.add_argument('style_path', help='The path of the created leaflet leafletStyles json.')
args = parser.parse_args()

print("Downloading %s file..." % args.kmz_url)
kmzData = req.urlopen(args.kmz_url).read()
print("Opening the KMZ file...")
kmzFile = io.BytesIO(kmzData)
z = zipfile.ZipFile(kmzFile)
[kmlPath] = [s for s in z.namelist() if s.endswith('.kml')]
kmlFile = z.open(kmlPath)
print("Converting the KMZ to GeoJSON...")
leafletStyles, geojson = k2g.convert(kmlFile, 'curves', 'leaflet')

print("Removing GeoJSON feature descriptions...")
for props in (f['properties'] for f in geojson['features']):
del props['description']

print("Filtering unused styles...")
usedStyles = set(f['properties']['styleUrl'] for f in geojson['features'])
leafletStyles = dict(filter(lambda pair: pair[0] in usedStyles, leafletStyles.items()))

print("Tweaking the styles...")
for style in leafletStyles.values():
# Recolour the styles to a magenta theme with brightness
hex = style['color'].lstrip('#')
rgb = tuple(int(hex[i:i+2], 16) for i in (0, 2, 4))
hls = colorsys.rgb_to_hls(*(c / 255 for c in rgb))
# Road quality is hue-coded by the "[0.16666..., -0.5] mod 1" range.
# This is transformed into a value-coded "[0.25, 0.75]" range.
# Transform start to 0; scale to [0, 0.5]; transform to [0.25, 0.75].
lightness = ((hls[0] - 0.16666666666666667) * -0.75 + 0.25)
# Ensure that value is within the valid range even for unexpected colours.
lightness = max(0.25, min(lightness, 0.75))
rgb = tuple(int(i * 255) for i in colorsys.hls_to_rgb(0.83, lightness, 1.0))
style['color'] = '#%02X%02X%02X' % rgb

# Make roads thinner by 0.5 pixel ([2, 3] -> [1, 2])
style['weight'] -= 0.5

# Reduce opacity range ([0.5, 1] -> [0.75 -> 1])
style['opacity'] = style['opacity'] / 2 + 0.5

print("Ensuring that the output directory exists...")
for outDir in [os.path.dirname(s) for s in [args.geojson_path, args.style_path]]:
outDir and os.makedirs(outDir, exist_ok=True)
print("Writing GeoJSON data to %s..." % args.geojson_path)
with open(args.geojson_path, 'w') as f:
json.dump(geojson, f, separators=(',', ':'))
print("Writing Leaflet style data to %s..." % args.style_path)
with open(args.style_path, 'w') as f:
json.dump(leafletStyles, f, separators=(',', ':'))

if __name__ == "__main__":
main()
895 changes: 0 additions & 895 deletions map/curves.geojson

This file was deleted.

15 changes: 0 additions & 15 deletions map/curves.md

This file was deleted.

74 changes: 42 additions & 32 deletions res/huroutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,17 @@ const huroutes = {
// Choosable overlay sources supported in the layer selector. All are off by default.
'overlays': {
'Elevation Shading': L.tileLayer('https://map.turistautak.hu/tiles/shading/{z}/{x}/{y}.png', {attribution:'© turistautak.hu',minZoom:5,maxZoom: 18,zIndex:5,className: 'overlay-dem'}),
'Curvature': L.layerGroup(null, {attribution:'Curves: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'})
'Curvature': L.layerGroup()
},
// Overlay sources that are always shown and hidden along with specific map tile sources.
// The name of the overlay must be the same as the tile layer to which it is bound.
'tileOverlays': {},
// Lazy-content overlay data, which is downloaded only when the user first shows it.
// Usage: Create an overlay under 'overlays' with an empty L.layerGroup. Create an
// item in 'lazyOverlays' with the same ID as the layergroup overlay. The value is
// a function that returns the layer that contains the data.
'lazyOverlays': {
'Curvature': (layer) => omnivore.geojson('map/curves.geojson', null, layer)
// Curvature data definition, used to populate the Curvature overlay when first viewed.
// The layer's data is prepared by the build-website github action.
'curvatureData': {
'geojson': 'map/curves.geojson',
'style': 'map/curves-style.json',
'attribution': 'Curves: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}
},
// A list of navigation service providers that can be chosen for the "navigate to" links'.
Expand Down Expand Up @@ -203,16 +203,7 @@ $(document).ready(function() {
map.getPane('bkgRoutes').style.zIndex = 450;

// Asynchronously loads the huroutes database
$.getJSON('data.json', initializeContent)
.fail(() => {
err = () => console.error('Failed loading the route database.');
// Google Translate workaround for correcting database path
gtBase = getGoogleTranslateBase();
if (gtBase)
$.getJSON(gtBase + 'data.json', initializeContent).fail(err);
else
err();
});
getJSON('data.json', initializeContent);

// Initializing misc. huroutes app components
initColorSelector();
Expand Down Expand Up @@ -320,23 +311,27 @@ function initCtrls(tiles, overlays)
/**
* Initializes data inside of a layer group on demand.
* @param {string} id The overlay identifier.
* @param {object} layerGroup A Leaflet layerGroup object to which the data is added.
* @param {object} lg A Leaflet layerGroup object to which the data is added.
*/
function initLazyOverlay(id, layerGroup)
function initLazyOverlay(id, lg)
{
try {
if (layerGroup.getLayers().length == 0)
{
var layer = L.geoJson(null, {
style: {
color: '#BB0',
opacity: 0.9,
weight: 1.5
}
});
layerGroup.addLayer(huroutes.opt.map.lazyOverlays[id](layer));
}
} catch { }
// Only populate empty layerGroups with data.
if (!lg.getLayers || lg.getLayers().length != 0)
return;

// Initialize the Curvature lazy overlay's contents.
if (id == 'Curvature')
{
const cfg = huroutes.opt.map.curvatureData;
$.when(getJSON(cfg.geojson), getJSON(cfg.style)).done((r1, r2) => {
const styleMap = r2[0];
lg.addLayer(L.geoJson(r1[0], {
attribution: cfg.attribution,
style: (feature) =>
feature.properties.styleUrl && styleMap[feature.properties.styleUrl]
}));
});
}
}

/**
Expand Down Expand Up @@ -1168,6 +1163,21 @@ function initSidebarEvents()
})
}

/**
* Does the same as $.getJSON, but works around Google Translate relative URL issues.
*/
function getJSON(url, ...args)
{
return $.getJSON(url, ...args).then(data => data, err => {
msg = () => console.error('Failed loading {0}.'.format(url));
gtBase = getGoogleTranslateBase();
if (gtBase)
return $.getJSON(gtBase + url, ...args).fail(msg);
msg();
return err;
});
}

// TODO: Remove me
function upgradeConfig() {
const convert = (obj, val) => Object.keys(obj).find(key => obj[key] === val) || val;
Expand Down

0 comments on commit 2e6c153

Please sign in to comment.