openfreemap icon indicating copy to clipboard operation
openfreemap copied to clipboard

Outdoor style possible?

Open ravenfeld opened this issue 1 year ago • 23 comments

I wanted to propose an outdoor style to openfreemap but I have the impression that it lacks data to succeed in doing something like.

https://www.maptiler.com/maps/outdoor/

After all, I'm just starting out, so maybe I'm looking in the wrong place, but the relationship between a hiking trail doesn't seem to be there for the drawing or the trail_visibility tag.

I'm having trouble I suppose because I know OSM and I can't manage to do the mapping properly.

ravenfeld avatar Oct 18 '24 18:10 ravenfeld

I never made an outdoor map, but I believe there are two components required:

  • elevation data, to create hillshading and contour lines
  • hiking specific tags, etc.

Elevation data is tracker here: https://github.com/hyperknot/openfreemap/issues/19

For hiking specific tags, you'd need to use the inspector and see what is and what isn't visible. Can you check for the tags using the inspector? https://github.com/hyperknot/openfreemap/blob/main/docs/debugging_names.md

hyperknot avatar Oct 21 '24 09:10 hyperknot

We are using maptiler outdoor map extensively at osmapp.org – there are 2 vector map sources in their outdoor style:

  1. standard maptiler_planet, which is the same as OpenFreeMap planet
  2. specific hiking source outdoor, which holds the geometries of the marked trails and some additional hiking POIs – https://api.maptiler.com/tiles/outdoor/tiles.json?key=${apiKey}, see source: 'outdoor' in the style here

Of course there are also 2 sources for digital elevation models (terrain-rgb + countours), but this is probably beyond the scope of OFR project. See here.

I wonder how much bigger would get the OFR planet, if we included the hiking tags. Maptiler decided to split it in two, but if I understand it correctly, they are basically duplicating all the geometries in both tiles, so just adding the hiking tags in OFR planet could be doable and awesome! 🙂

zbycz avatar Nov 15 '24 09:11 zbycz

I'd love to include the hiking tags. I don't have capacity to dedicate for this effort, but if you can figure out how to include custom tags in planetiler, I'd be happy to include those in the official OFM planet source. I'd recommend opening an issue/discussion in planetiler GitHub.

Terrain related data will be included in the long term future: https://github.com/hyperknot/openfreemap/issues/19

hyperknot avatar Nov 18 '24 14:11 hyperknot

I don't have the skills yet but I'm trying to understand how it works. If one day I can advance the technical subject I would be delighted.

ravenfeld avatar Nov 22 '24 08:11 ravenfeld

There'll be ready-made styles when this is done, you won't need to have advanced technical understanding to add outdoor maps to a website/app at that point.

hyperknot avatar Nov 22 '24 10:11 hyperknot

Quick question, do you host data in pmTiles or mbtiles? Because you can create your own profile for planetiler and add the information.

ravenfeld avatar Aug 17 '25 16:08 ravenfeld

PMTiles is purpose built for serverless hosting on AWS or Cloudflare. You upload the huge file to a R2 bucket and deploy a worker in front of it. It is explained here: https://docs.protomaps.com/deploy/cloudflare

If the question is how is OpenFreeMap storing vector tiles, it's neither, it's using a Btrfs disk image.

hyperknot avatar Aug 17 '25 17:08 hyperknot

I wanted to know if to make the tiles the command of this style was used: java -Xmx1g -jar planetiler.jar --download --area=monaco

Because if so, we can create a file that allows us to overload the existing profile to add tags.

ravenfeld avatar Aug 17 '25 17:08 ravenfeld

OK, now I understand your question.

This is the command being run: https://github.com/hyperknot/openfreemap/blob/a3eeed00b5eedbbd40568bc61bc0d6589ba0c710/modules/tile_gen/tile_gen_lib/planetiler.py#L35-L49

And yes, I'm open to adding tourist map specific tags to the default profile, such that one day we can create maps like WayMarkedTrails:

Image

hyperknot avatar Aug 17 '25 18:08 hyperknot

By simplifying the documentation and using Java 22, we can create profiles.

So I tested it by inheriting from the base profile to get all the information we already use and adding an outdoor layer that could contain additional information.

I may have added a bit too much, but I was just testing. I wanted to get the surface area with the real OSM information plus the tags that I find interesting for hiking. I also looked at how to find the relationships.

Currently I am running this command

java -cp planetiler.jar CustomProfile.java

And in my data directory I have the custom.pmtiles file which is exported

For test: http://83.228.195.237:8888/data/rando/#15.54/45.232238/5.757771

import com.onthegomap.planetiler.Profile;
import com.onthegomap.planetiler.config.Arguments;
import com.onthegomap.planetiler.reader.SourceFeature;
import com.onthegomap.planetiler.FeatureCollector;
import com.onthegomap.planetiler.Planetiler;
import java.nio.file.Path;
import org.openmaptiles.OpenMapTilesProfile;
import org.openmaptiles.layers.Transportation;
import com.onthegomap.planetiler.util.Translations;
import com.onthegomap.planetiler.config.PlanetilerConfig;
import com.onthegomap.planetiler.stats.Stats;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import com.onthegomap.planetiler.reader.osm.OsmRelationInfo;
import com.onthegomap.planetiler.reader.osm.OsmElement;
import java.util.ArrayList;


public class CustomProfile extends OpenMapTilesProfile {


	public CustomProfile(Planetiler runner) {
        super(runner.translations(), runner.config(), runner.stats());
    }

    public CustomProfile(Translations translations, PlanetilerConfig config, Stats stats) {
        super(translations,config,stats);
    }
        
    @Override
    public void processFeature(SourceFeature sourceFeature, FeatureCollector features) {
        super.processFeature(sourceFeature, features);

        // Puis ajoute sac_scale à transportation
        if (sourceFeature.canBeLine() && sourceFeature.hasTag("highway") &&
            (
	            sourceFeature.hasTag("sac_scale") || 
	            sourceFeature.hasTag("surface") ||
	            sourceFeature.hasTag("trail_visibility") ||
	            sourceFeature.hasTag("tracktype")
	    )) {

            var line = features.line("outdoor")
                            .inheritAttrFromSource("highway"); // récupère d'autres attributs utiles

            if (sourceFeature.hasTag("tracktype")) {
                line.setAttr("tracktype", sourceFeature.getTag("tracktype"));
            }
        
            if (sourceFeature.hasTag("sac_scale")) {
                line.setAttr("sac_scale", sourceFeature.getTag("sac_scale"));
            }

            if (sourceFeature.hasTag("surface")) {
                line.setAttr("surface", sourceFeature.getTag("surface"));
            }

            if (sourceFeature.hasTag("trail_visibility")) {
                line.setAttr("trail_visibility", sourceFeature.getTag("trail_visibility"));
            }
       
            int i = 0;
            for (var routeInfo : sourceFeature.relationInfo(RouteRelationInfo.class)) {
                    // (routeInfo.role() also has the "role" of this relation member if needed)
                    RouteRelationInfo relation = routeInfo.relation();
    
                    line.setAttr("relation_"+i+"_name", relation.name);
                    line.setAttr("relation_"+i+"_ref", relation.ref);
                    line.setAttr("relation_"+i+"_route", relation.route);
                    line.setAttr("relation_"+i+"_network", relation.network);
                    line.setAttr("relation_"+i+"_symbol", relation.symbol);
                    line.setAttr("relation_"+i+"_color", relation.color);
                    i++;
            }

        }
    }

    private record RouteRelationInfo(
        // OSM ID of the relation (required):
        @Override long id,
        // Values for tags extracted from the OSM relation:
        String name, String ref, String route, String network,String symbol,String color
    ) implements OsmRelationInfo {}

    @Override
    public List<OsmRelationInfo> preprocessOsmRelation(OsmElement.Relation relation) {
        // If this is a "route" relation ...
        if (relation.hasTag("type", "route")) {
        // where route=bicycle or route=mtb ...
        if (relation.hasTag("route","hiking")) {
            // then store a RouteRelationInfo instance with tags we'll need later
            return List.of(new RouteRelationInfo(
            relation.id(),
            relation.getString("name"),
            relation.getString("ref"),
            relation.getString("route"),
            relation.getString("network", ""),
            relation.getString("osmc:symbol", ""),
            relation.getString("colour", "")
            ));
        }
        }
        // for any other relation, return null to ignore
        return null;
    }

    public static void main(String[] args) {
        // arguments CLI avec download par défaut
        var arguments = Arguments.fromArgs(args).withDefault("download", true);

        // création de Planetiler
        Planetiler runner = Planetiler.create(arguments);

        // profil personnalisé
        var profile = new CustomProfile(runner);

        // exécution
        runner.setProfile(profile)
              .addOsmSource("osm", Path.of("data.osm.pbf"))
              .overwriteOutput(Path.of("data", "custom.pmtiles"))
              .run();
    }

}

ravenfeld avatar Aug 17 '25 18:08 ravenfeld

The idea is not that relationships like waymarked trail are also to display in a more relevant way a visible path from a path that is not, from a path that is part of a network. There is a debate currently on OSM on hiking trails because many renderings display the same thing for completely different paths.

ravenfeld avatar Aug 17 '25 18:08 ravenfeld

At first I wanted to make a data source just to add information like maptiler but I realized that when we do that we don't guarantee the data and suddenly we can have a desynchronization and display 2 paths when it should only be one Example with maptiler outdoor: Image

ravenfeld avatar Aug 17 '25 18:08 ravenfeld

You mean Maptiler Outdoor is using an hiking overlay on top of their normal map layer?

Is there any "standard" or is the community converging on something which would be a good base for creating such schema?

I'm sure someone must have created OpenMapTiles based hiking maps. Is there any open source schema for it?

hyperknot avatar Aug 17 '25 18:08 hyperknot

Yes, last year, Maptiler added an outdoor source to display hiking relation.

I haven't found anything along these lines. I have the impression that everyone renders for cars and then deviates a bit for outdoor use, but without going into detail.

I know of raster renderers in France that are good, but they can't currently be recreated in vector without information on bag_scale and trail_visibility.

ravenfeld avatar Aug 17 '25 18:08 ravenfeld

If you can research what would make a good looking global hiking map and give a recommendation for a planetiler profile, I'm happy to include it. It'd be nice to render something which looks nice and is usable on France, Germany, Austria, etc. especially as they might use incompatible systems.

hyperknot avatar Aug 17 '25 18:08 hyperknot

Do you want me to do the styling already?

ravenfeld avatar Aug 17 '25 19:08 ravenfeld

Sure, if you are open to do it, then it'd be fantastic! I don't know if you follow the other thread, but Mapterhorn just released these amazing terrain/hillshade dataset, you can use that: https://github.com/hyperknot/openfreemap/issues/19#issuecomment-3174527733

https://mapterhorn.com/

hyperknot avatar Aug 17 '25 19:08 hyperknot

I think the best resource on rendering hiking OSM tags is here: https://hiking.waymarkedtrails.org/#help-rendering

What do you think, we can start with a basic version of this and keep adding to the planetiler profile later on?

hyperknot avatar Aug 17 '25 19:08 hyperknot

I have to look into how to do it, currently I can't do it, I have the impression that I have to have https for it to work

For you, the need is therefore to display the relations first, which can be complicated because a path can be in x relations and so which one should you draw? the international one if it exists otherwise the national one if it exists ....

ravenfeld avatar Aug 17 '25 19:08 ravenfeld

I mean you can just link to the hosted https://mapterhorn.com/ pmtiles, can't you? If you need https locally, the simplest I found was to use cloudflare tunnel. It's a free ngrok alternative.

About the relations, I don't yet know as much as you, but I imagine the ultimate goal to be something like this, where E4 is international and the others are local, and they are all rendered nicely, next to each other. This might be crazily complicated/impossible in planetiler + MapLibre, so it's just a reference.

Image

Thunderforest does this, which might be technically simpler:

Image

MapToolkit does this: Image

Again, I don't yet know technically which of these might be simple and which are complicated.

It'd be nice to come up with a system for the:

  • line of the route, like color, width, dashed, pointed, etc.
  • tags to show on the route, like on the screenshots above

hyperknot avatar Aug 17 '25 19:08 hyperknot

For the https, it's because the server I'm using is HTTP, and therefore Mapunik doesn't allow me to reference the tiles I've placed on it. I'll look into Cloudflare.

ravenfeld avatar Aug 17 '25 19:08 ravenfeld

Sure, Cloudflare Tunnels will solve it, even if it's a local dev server. If it's a public server, just deploy Cloudflare in front of it (I mean set up an A record for that server + enable the "orange cloud" button).

hyperknot avatar Aug 17 '25 19:08 hyperknot

Nice post about using Mapterhorn https://jonathanlurie.substack.com/p/basemapkit-lets-explore-the-mountains

hyperknot avatar Aug 26 '25 17:08 hyperknot