Drop the OsmAnd-recolor map theme, add offline labels and a lighter palette nudge
Remove osmand-style.js (the per-layer color-patching approach) and its docs entirely — back to the plain OpenFreeMap "liberty" style online, and protomaps-themes-base's stock theme offline as the base. Offline maps previously had no text labels at all: the protomaps-themes-base layers() call never passed a lang option, which is required to include its label layers. Fixed, backed by vendored Noto Sans Regular/Italic/Medium glyph PBFs (ranges 0-255 and 256-511 only — Basic Latin + Latin-1 Supplement + Latin Extended-A, enough for accented Central European place names; other scripts won't render offline). Verified against the actual offline.pmtiles vector layer schema that the name/name:en/script fields this expects are present. Also nudge a handful of OFFLINE_THEME color roles (background, water, wood, buildings, the two road-importance tiers) towards the online style's actual palette (sampled from its style JSON) so switching Online/Offline doesn't look like two different apps — deliberately partial, not the removed full recolor.
This commit is contained in:
1 parent
06ea8ea141
commit
0887ccbfe7
12 files changed
+163
-217
No files matched your search
+28
-8
@@ -7,24 +7,41 @@ import {
|
||||
import { RadarControl } from "/js/radar.js";
|
||||
import { GpxPanelControl } from "/js/gpx.js";
|
||||
import { GnssControl } from "/js/gnss.js";
|
||||
import { onlineStyle, offlineTheme } from "/js/osmand-style.js";
|
||||
import { LAYERS_ICON } from "/js/icons.js";
|
||||
import { notifySidePanelOpen, onlyOneSidePanelOpen } from "/js/panels.js";
|
||||
|
||||
const CENTER = [17.1077, 48.1486];
|
||||
const ZOOM = 13;
|
||||
const ONLINE_STYLE = "https://tiles.openfreemap.org/styles/liberty";
|
||||
|
||||
const protocol = new pmtiles.Protocol();
|
||||
addProtocol("pmtiles", protocol.tile);
|
||||
|
||||
// No label layers here (see docs/architecture.adoc): rendering text needs a
|
||||
// glyphs server, which the offline style deliberately does not depend on.
|
||||
// A few color roles nudged from protomaps-themes-base's stock "light" theme
|
||||
// towards the online OpenFreeMap "liberty" style's actual palette (sampled
|
||||
// from its style JSON — see docs/architecture.adoc), so switching between
|
||||
// Online/Offline doesn't look like two different apps. Not a full recolor:
|
||||
// most roles (labels, casings, minor terrain fills, ...) are left at their
|
||||
// theme defaults.
|
||||
const OFFLINE_THEME = {
|
||||
...protomaps_themes_base.namedTheme("light"),
|
||||
background: "#f8f4f0",
|
||||
earth: "#f8f4f0",
|
||||
water: "#9ebdff",
|
||||
wood_a: "#ace38c",
|
||||
wood_b: "#ace38c",
|
||||
buildings: "#dcd9d6",
|
||||
highway: "#f2934a",
|
||||
major: "#ffeeaa",
|
||||
link: "#ffeeaa",
|
||||
};
|
||||
|
||||
function offlineStyle() {
|
||||
const theme = offlineTheme(protomaps_themes_base.namedTheme("light"));
|
||||
const pmtilesUrl = `${window.location.origin}/tiles/offline.pmtiles`;
|
||||
|
||||
return {
|
||||
version: 8,
|
||||
glyphs: `${window.location.origin}/vendor/fonts/{fontstack}/{range}.pbf`,
|
||||
sources: {
|
||||
offline: {
|
||||
type: "vector",
|
||||
@@ -32,7 +49,10 @@ function offlineStyle() {
|
||||
attribution: "© OpenStreetMap contributors",
|
||||
},
|
||||
},
|
||||
layers: protomaps_themes_base.layers("offline", theme),
|
||||
// lang: "en" pulls in labels_layers (see docs/architecture.adoc) — only
|
||||
// the Latin-script glyph ranges are vendored, so labels using other
|
||||
// scripts won't render offline.
|
||||
layers: protomaps_themes_base.layers("offline", OFFLINE_THEME, { lang: "en" }),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -83,7 +103,7 @@ class LayerToggleControl {
|
||||
|
||||
const map = new MaplibreMap({
|
||||
container: "map",
|
||||
style: await onlineStyle(),
|
||||
style: ONLINE_STYLE,
|
||||
center: CENTER,
|
||||
zoom: ZOOM,
|
||||
});
|
||||
@@ -108,9 +128,9 @@ new GpxPanelControl(map).mount(sidebar);
|
||||
new RadarControl(map).mount(sidebar);
|
||||
new GnssControl(map).mount(sidebar);
|
||||
|
||||
async function applyStyle(name) {
|
||||
function applyStyle(name) {
|
||||
layerToggle.setActive(name);
|
||||
map.setStyle(name === "offline" ? offlineStyle() : await onlineStyle());
|
||||
map.setStyle(name === "offline" ? offlineStyle() : ONLINE_STYLE);
|
||||
}
|
||||
|
||||
// If a local PMTiles file is present, prefer it as the default style — its
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
// Approximates the look of OsmAnd's own map render (osmand.net/map — muted
|
||||
// earth tones, cream background, orange roads) without depending on their
|
||||
// tile server: OsmAnd's terms restrict tile use to "personal, non-commercial
|
||||
// use within OsmAnd applications and platforms", which this app isn't.
|
||||
//
|
||||
// One shared palette drives two independent recolorings so Online and
|
||||
// Offline look consistent: OpenFreeMap's "liberty" vector style (paint
|
||||
// colors patched in place, see recolorLayer) for Online, and a custom
|
||||
// protomaps-themes-base Theme (see offlineTheme) for Offline.
|
||||
|
||||
const PALETTE = {
|
||||
background: "#f1ecd7",
|
||||
water: "#aad3df",
|
||||
wood: "#c8dfb0",
|
||||
park: "#d3e8b8",
|
||||
landuse: "#ece4c6",
|
||||
building: "#d9cead",
|
||||
buildingOutline: "#c7bb92",
|
||||
roadMajor: "#eb9d43",
|
||||
roadMajorCasing: "#c9822c",
|
||||
roadMinor: "#fdf8ee",
|
||||
roadMinorCasing: "#cfc09a",
|
||||
boundary: "#a98fc2",
|
||||
};
|
||||
|
||||
const LIBERTY_STYLE_URL = "https://tiles.openfreemap.org/styles/liberty";
|
||||
let cachedOnlineStyle = null;
|
||||
|
||||
// Fetches OpenFreeMap's "liberty" style and recolors it in place. Cached —
|
||||
// the recoloring is pure, and re-fetching on every Online/Offline toggle
|
||||
// would be wasteful.
|
||||
export async function onlineStyle() {
|
||||
if (cachedOnlineStyle) return cachedOnlineStyle;
|
||||
|
||||
const response = await fetch(LIBERTY_STYLE_URL);
|
||||
const style = await response.json();
|
||||
style.layers = style.layers.map(recolorLayer);
|
||||
cachedOnlineStyle = style;
|
||||
return style;
|
||||
}
|
||||
|
||||
// Overrides the color roles that define the "look" on top of a
|
||||
// protomaps-themes-base Theme (e.g. namedTheme("light")), leaving roles it
|
||||
// doesn't set (labels, rail, tunnels, POI colors) at their defaults.
|
||||
export function offlineTheme(baseTheme) {
|
||||
return {
|
||||
...baseTheme,
|
||||
background: PALETTE.background,
|
||||
earth: PALETTE.background,
|
||||
water: PALETTE.water,
|
||||
wood_a: PALETTE.wood,
|
||||
wood_b: PALETTE.wood,
|
||||
park_a: PALETTE.park,
|
||||
park_b: PALETTE.park,
|
||||
scrub_a: PALETTE.park,
|
||||
scrub_b: PALETTE.park,
|
||||
pedestrian: PALETTE.landuse,
|
||||
industrial: PALETTE.landuse,
|
||||
school: PALETTE.landuse,
|
||||
hospital: PALETTE.landuse,
|
||||
buildings: PALETTE.building,
|
||||
highway_casing_early: PALETTE.roadMajorCasing,
|
||||
highway_casing_late: PALETTE.roadMajorCasing,
|
||||
highway: PALETTE.roadMajor,
|
||||
major_casing_early: PALETTE.roadMajorCasing,
|
||||
major_casing_late: PALETTE.roadMajorCasing,
|
||||
major: PALETTE.roadMajor,
|
||||
link_casing: PALETTE.roadMajorCasing,
|
||||
link: PALETTE.roadMajor,
|
||||
minor_casing: PALETTE.roadMinorCasing,
|
||||
minor_service_casing: PALETTE.roadMinorCasing,
|
||||
minor_a: PALETTE.roadMinor,
|
||||
minor_b: PALETTE.roadMinor,
|
||||
other: PALETTE.roadMinor,
|
||||
minor_service: PALETTE.roadMinor,
|
||||
boundaries: PALETTE.boundary,
|
||||
};
|
||||
}
|
||||
|
||||
const FILL_PROP = /^(background-color|fill-color|line-color|fill-extrusion-color)$/;
|
||||
const OUTLINE_PROP = /outline-color$/;
|
||||
|
||||
const FILL_COLOR = {
|
||||
background: PALETTE.background,
|
||||
water: PALETTE.water,
|
||||
wood: PALETTE.wood,
|
||||
park: PALETTE.park,
|
||||
landuse: PALETTE.landuse,
|
||||
building: PALETTE.building,
|
||||
roadMajor: PALETTE.roadMajor,
|
||||
roadMinor: PALETTE.roadMinor,
|
||||
boundary: PALETTE.boundary,
|
||||
};
|
||||
|
||||
const OUTLINE_COLOR = {
|
||||
building: PALETTE.buildingOutline,
|
||||
roadMajor: PALETTE.roadMajorCasing,
|
||||
roadMinor: PALETTE.roadMinorCasing,
|
||||
park: "#bcdb9a",
|
||||
};
|
||||
|
||||
// Maps a "liberty" style layer id to one of the categories above, based on
|
||||
// the id naming conventions OpenFreeMap's style uses (verified against its
|
||||
// actual layer list — see docs/architecture.adoc). Returns null for
|
||||
// anything left at its original color: labels/icons (all "symbol" layers,
|
||||
// filtered by type below before this even runs), railways, and minor
|
||||
// terrain fills (ice/wetland/sand) that aren't part of OsmAnd's signature
|
||||
// look.
|
||||
function categoryFor(layerId) {
|
||||
if (layerId === "background") return "background";
|
||||
if (/rail/.test(layerId)) return null;
|
||||
if (/^water/.test(layerId)) return "water";
|
||||
if (/^landcover_wood/.test(layerId)) return "wood";
|
||||
if (/^(park|landcover_grass)/.test(layerId)) return "park";
|
||||
if (/^landuse/.test(layerId)) return "landuse";
|
||||
if (/^building/.test(layerId)) return "building";
|
||||
if (/^(road|bridge|tunnel)_(motorway|trunk_primary)/.test(layerId)) return "roadMajor";
|
||||
if (/^(road|bridge|tunnel)_/.test(layerId)) return "roadMinor";
|
||||
if (/^boundary/.test(layerId)) return "boundary";
|
||||
return null;
|
||||
}
|
||||
|
||||
const RECOLORABLE_TYPES = new Set(["background", "fill", "line", "fill-extrusion"]);
|
||||
|
||||
function recolorLayer(layer) {
|
||||
if (!RECOLORABLE_TYPES.has(layer.type) || !layer.paint) return layer;
|
||||
|
||||
const category = categoryFor(layer.id);
|
||||
if (!category) return layer;
|
||||
|
||||
const paint = { ...layer.paint };
|
||||
for (const [prop, value] of Object.entries(paint)) {
|
||||
if (OUTLINE_PROP.test(prop) && OUTLINE_COLOR[category]) {
|
||||
paint[prop] = replaceColors(value, OUTLINE_COLOR[category]);
|
||||
} else if (FILL_PROP.test(prop)) {
|
||||
paint[prop] = replaceColors(value, FILL_COLOR[category]);
|
||||
}
|
||||
}
|
||||
|
||||
return { ...layer, paint };
|
||||
}
|
||||
|
||||
const COLOR_PATTERN = /^#[0-9a-f]{3,8}$/i;
|
||||
const COLOR_FN_PATTERN = /^(rgb|hsl)a?\(/i;
|
||||
|
||||
// Walks a paint value — a flat color string, or a MapLibre expression like
|
||||
// ["interpolate", ["linear"], ["zoom"], 5, "hsl(26,87%,62%)", 6, "#fc8"] —
|
||||
// and swaps every color-shaped string for `newColor`, leaving operators,
|
||||
// zoom/get references and numbers untouched. Generic across expression
|
||||
// types (interpolate/match/case/step/...) since it never inspects the
|
||||
// expression's operator, only whether each leaf looks like a color.
|
||||
function replaceColors(value, newColor) {
|
||||
if (typeof value === "string") {
|
||||
return COLOR_PATTERN.test(value) || COLOR_FN_PATTERN.test(value) ? newColor : value;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => replaceColors(item, newColor));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Vendored
+93
@@ -0,0 +1,93 @@
|
||||
Copyright 2022 The Noto Project Authors (https://github.com/notofonts)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Reference in new issue
Block a user