Avoid or include a large amount of POIs in route-planning

Hello there!

I am new to Graphhopper and wanted to ask how I should best tackle two specific use-cases:

I want to create a small app that allows users to plan routes from point A to B, just within one city (Vienna, Austria). Within this city, I have a large amount of points with lat/long, around 228.000, of different categories.

The route planning should either:

  • Avoid the vicinity of certain categories of points along the route, if possible.
  • Visit certain categories of points along the route, if it is not a big detour.

One route planning would only target a few categories, e.g. avoid points of categories A, B, C, D (11.153 points), or visit points of category F (9.400 points), along a route from A to B.

I thought I would start with avoiding certain points. I have looked at the blog post Examples for customizing routes, where it shows how a single polygon can be avoided.

Just to get a Proof-of-Concept going, I have tried to create a custom model to avoid multiple polygons. More specifically, I have drawn a 10m square around all points from one category and included some of them in the custom model. It looks like this:

{
    "priority": [
      {
        "if": "in_avoid",
        "multiply_by": "0"
      }
    ],
    "areas": {
      "type": "FeatureCollection",
      "features": [
        {
          "type": "Feature",
          "id": "avoid",
          "geometry": {
            "type": "Polygon",
            "coordinates": [
              [
                [
                  16.302208745133793,
                  48.223221470959245
                ],
                [
                  16.302478416713324,
                  48.223221470959245
                ],
                [
                  16.302478416713324,
                  48.22340113401607
                ],
                [
                  16.302208745133793,
                  48.22340113401607
                ],
                [
                  16.302208745133793,
                  48.223221470959245
                ]
              ],
              [
                [
                  16.302200703283482,
                  48.223171196872364
                ],
                [
                  16.30247037459815,
                  48.223171196872364
                ],
                [
                  16.30247037459815,
                  48.22335085992919
                ],
                [
                  16.302200703283482,
                  48.22335085992919
                ],
                [
                  16.302200703283482,
                  48.223171196872364
                ]
              ],
              ....
            ]
          }
        }
      ]
    }
  }

But while the squares denoting the points are shown successfully on the map, it doesn’t seem to work. See this demo.

Additionally, I was only able to include a small number of points in the request, as it results in a status 400 error: “Custom model cannot use more than 100 000 characters.” or 414: “Request-URI Too Large” very quickly. Of course, it doesn’t make sense to submit ten-thousands of polygons with each request. It was just meant as a POC, to see if I could avoid multiple points with Graphhopper. Could someone point out what I am doing wrong? How do I successfully avoid multiple squares? Is there a different, more suitable approach?

For any further development, I would need to setup my own Graphhopper instance, where the points with categories are already saved on the server and the request only submits “avoid category A,B,C” or “include category F”. Is something like this possible? Can you add a dataset of polygons, each with a category property to the Graphhopper instance, and then configure a request to include or avoid those polygons based on their property?

I am thankful for any pointers in the right direction :+1:

But while the squares denoting the points are shown successfully on the map

As stated in the documentation: for an “area” at the moment currently only a single Polygon is supported. However you could create multiple areas and use them in the custom model for exclusion/avoidance.

Additionally, I was only able to include a small number of points in the request, as it results in a status 400 error: “Custom model cannot use more than 100 000 characters.”

We placed a limit for the custom model at some point to avoid certain types of attacks for public facing GraphHopper services. You can reduce the precision of the coordinates or reduce the number of points per polygon to avoid this.

The “Request-URI Too Large” error should not happen as the /route endpoint with custom_model is only POST request. (probably you tried GH Maps? Then this error won’t occur in production)

Hello @karussell, thanks for the answer!

I have just tried creating an area for each point, and it seems to work for a limited number of points. (As a sidenote, is there a way to refer to all areas at once in the if condition, to avoid doing in_area1 || in_area2 || ... || in_area20?)

However, if I add enough areas/points to represent my usecase (e.g. 2000 areas/points), I get a StackOverflowException. This is on a locally run GH instance, using the POST /route.

2025-07-04 12:46:57.279 [pool-2-thread-10 - POST /route?key=] ERROR i.d.j.errors.LoggingExceptionMapper - Error handling a request: 9fff1654956773e2
java.lang.StackOverflowError: null
        at com.graphhopper.routing.weighting.custom.ConditionalExpressionVisitor.visitRvalue(ConditionalExpressionVisitor.java:32)
        at org.codehaus.janino.Java$Rvalue.accept(Java.java:4495)

So it doesn’t seem to scale using this approach, which is a pity, because avoiding these points works like I would want it to, as a Proof of Concept :thinking:

Is there a way to save the points/polygons directly on the GH server (similar to how the GH server has the OpenStreetMap PBF data) and then refer to them as a certain type/category in the if condition of the request?

The “Request-URI Too Large” error should not happen as the /route endpoint with custom_model is only POST request. (probably you tried GH Maps? Then this error won’t occur in production)

Yes, the 414: “Request-URI Too Large” error appears when I use GH Maps :+1:

Currently there is no support for so many areas as this is not the use case. If you have so many locations likely the preferred way would be to block this on the server side via blocking access.

I get a StackOverflowException

Could be a limitation of janino. You could try to increase the allowed stack calls of the JVM.

Is there a way to save the points directly on the GH server

Currently not out of the box. You could modify the PBF and include access restrictions directly.

I tried increasing the stack size via -Xss512M when running GH and it does not throw a StackOverflowException anymore, instead it logs this exception as info (and also sends it as body of a 400 Bad Request response):

2025-07-04 16:14:32.741 [pool-2-thread-17 - POST /route?key=] INFO c.g.http.MultiExceptionMapper - bad request: [java.lang.IllegalArgumentException: Cannot compile expression: Compiling "JaninoCustomWeightingHelperSubclass3" in File 'source', Line 15, Column 8: File 'source', Line 2019, Column 16: Compiling "init(CustomModel customModel, EncodedValueLookup lookup, Map<String, com.graphhopper.util.JsonFeature> areas)"]

Again something to do with Janino. Are there some other configurations I need to set alongside the stack size?

Currently not out of the box. You could modify the PBF and include access restrictions directly.

I think if I modify the PBF by including the points or polygons, they would not be accessible when planning routes with GH, right? Since route planning only looks at road attributes (besides blocked areas that are passed along with the request). And if I modify the road segments/edges near points in the PBF, the blocking is static and permanent, not dynamic anymore :thinking:

Or is there a way I could refer to polygons that are already included in the PBF as blocked areas when planning routes? I am guessing that even if this was possible, it might lead to the same errors as above, when passing those same blocked areas along with the request?

This error message sounds strange. But what you try (thousands of exclusion areas) is out of spec at the moment and not really tested.

Instead I recommend you change the access after the import via the Java API directly.

I think if I modify the PBF by including the points or polygons, they would not be accessible when planning routes with GH, right?

You would need to modify existing ways (depending on the polygons) and add (access) tags based on which vehicles it should affect.

Another way could be to modify the access tags via the low level Java API (location index to find the edges to change the encoded values)

Yes, you can use a feature called ‘custom areas’. This is how GraphHopper sets the country encoded value as well. You can use a geojson file with all your areas and for each edge the matching areas will be available in the tag parsers. This way you could either block access to these edges in the access parser or create your own encoded value that you can use in your custom model (either on the client or server side).

1 Like

Anything you do in a client custom model that you pass along with the request you can also do with a server-side custom model you setup in your configuration file.

We did some work towards using OSM relations to set road attributes for roads located within the area of relations here: Add landuse encoded value based on closed-ring ways tagged with landuse=* by easbar ¡ Pull Request #2765 ¡ graphhopper/graphhopper ¡ GitHub. But for the use case you are describing I think using the custom area feature, or the using the location index like karussell suggested would be ideal.

Hello @easbar!

Thank you both for the suggestions :slight_smile:

I have now created an custom_areas.geojson file with all 226822 points as square polygons, saved in 644 MultiPolygon areas with IDs. The structure looks like this:

{
  "type": "FeatureCollection",
  "features": [
    {
	  "id": "area1",
      "type": "Feature",
      "geometry": {
		"type": "MultiPolygon",
        "coordinates": [[
			[
			  [
				16.377413176051093,
				48.24704497473225
			  ],
			  [
				16.37768297322466,
				48.24704497473225
			  ],
			  [
				16.37768297322466,
				48.247224637789074
			  ],
			  [
				16.377413176051093,
				48.247224637789074
			  ],
			  [
				16.377413176051093,
				48.24704497473225
			  ]
			],
			[
			  [
			    16.47512672251574,
			    48.23899983460768
			  ],
			  [
			    16.475396477258208,
			    48.23899983460768
			  ],
			  [
			    16.475396477258208,
			    48.23917949766451
			  ],
			  [
			    16.47512672251574,
			    48.23917949766451
			  ],
			  [
			    16.47512672251574,
			    48.23899983460768
			  ]
		    ],
		    ...
		]]
      }
    },
    {
	  "id": "area2",
      "type": "Feature",
      "geometry": {
		"type": "MultiPolygon",
        "coordinates": [[
          [
            [
              16.474311019585684,
              48.2393951680767
            ],
            [
              16.474580776412758,
              48.2393951680767
            ],
            [
              16.474580776412758,
              48.239574831133524
            ],
            [
              16.474311019585684,
              48.239574831133524
            ],
            [
              16.474311019585684,
              48.2393951680767
            ]
          ],
          [
            [
              16.47483251595713,
              48.23917987318572
            ],
            [
              16.47510227164894,
              48.23917987318572
            ],
            [
              16.47510227164894,
              48.239359536242546
            ],
            [
              16.47483251595713,
              48.239359536242546
            ],
            [
              16.47483251595713,
              48.23917987318572
            ]
          ],
          [
            [
              16.474431104903747,
              48.23934493174089
            ],
            [
              16.474700861465916,
              48.23934493174089
            ],
            [
              16.474700861465916,
              48.239524594797714
            ],
            [
              16.474431104903747,
              48.239524594797714
            ],
            [
              16.474431104903747,
              48.23934493174089
            ]
          ],
          ...
        ]]
      }
    },
    ...
  ]
}

I have loaded the file via the custom_areas.directory in config.yml, which seems to work, as it prints INFO com.graphhopper.GraphHopper - Will make 644 areas available to all custom profiles. Found in custom_areas.

To test whether it would allow me to avoid areas in the same way as before (when passing some points as indiviual areas in the request), I extended the car profile in the same config file:

profiles:
   - name: car
     custom_model: 
      {
       "distance_influence": 90,
       "priority": [
         { "if": "!car_access", "multiply_by": "0" },
         { "if": "road_access == DESTINATION || road_access == PRIVATE", "multiply_by": "0.1" },
         {
          "if": "in_area1 || in_area2 || in_area3",
          "multiply_by": "0"
         }
       ],
       "speed": [
        { "if": "true", "limit_to": "car_average_speed" }
       ]
      }

But this does not seem to work. When using the car profile in GH maps to plan a route, it uses streets which should be blocked by the custom areas. It does recognize the areas though, as when I write an invalid area in the if condition (e.g. areaX), the server doesn’t start (Error: Cannot compile expression: Area 'areaX' wasn't found).

Also, the custom areas don’t appear on GH maps like they would when specifying them within the request (as orange squares). Is there a way to visualize those imported areas in GH Maps, to check if the import worked correctly? Is them not showing up a sign that something is not working? :thinking:

Currently this is not implemented, no.

I just tried this myself and it worked as expected. Maybe try a single, larger area to be sure?

But actually this is not the way I recommend approaching this. I would add a custom tag parser and encoded value (you need to modify Java code for this). Then in the handleWayTags method you will implement for the tag parser you can call way.getTag("custom_areas", null) which will give you all the custom areas containing the current edge. Here you will be able to set the encoded value depending on these custom areas, and finally you can use this encoded value in the custom model. This will also allow you to use a dedicated value for subsets of your areas like, e.g. areas 3, 6 and 7, so you won’t need to use in_area3 || in_area6 | in_area7 in the custom model (not sure what your exact requirements are).

Also note that you can click 'Show Routing Graph` in the layers menu in GraphHopper Maps and hover the edges to see all their encoded values.