Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/better curves layer #172

Merged
merged 2 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.geo.json" "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.

76 changes: 44 additions & 32 deletions res/huroutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,19 @@ 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': {
// Workaround: cannot give it the "geojson" extension, because Google Translate
// blocks ajax requests to most extensions, but not json.
'geojson': 'map/curves.geo.json',
'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 @@ -201,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 @@ -318,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 @@ -1166,4 +1163,19 @@ function initSidebarEvents()
})
}

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

})();