diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000000..73fca788b7b --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,32 @@ +{ + "permissions": { + "allow": [ + "Bash(git checkout:*)", + "Bash(git add:*)", + "Bash(git rm:*)", + "Bash(awk:*)", + "Bash(xargs git add)", + "Bash(xargs git checkout:*)", + "Bash(xargs git rm:*)", + "Bash(git diff:*)", + "Bash(mvn clean:*)", + "Bash(rm:*)", + "Bash(javac:*)", + "Bash(mvn compile:*)", + "Bash(java:*)", + "Bash(mvn test:*)", + "WebSearch", + "Bash(echo:*)", + "Bash(echo $PATH)", + "Bash(mvn:*)", + "Read(//Users/oleksii/.m2/**)", + "Read(//Library/Java/JavaVirtualMachines/**)", + "Read(//Users/oleksii/**)", + "Bash(timeout 60 mvn test:*)", + "Read(//tmp/**)", + "Bash(tee:*)" + ], + "deny": [], + "ask": [] + } +} \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..82bd03b9f9a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,16 @@ +# Repository Guidelines + +## Project Structure & Module Organization +GraphHopper is a multi-module Maven project. `core/` hosts the routing engine (`core/src/main/java`) and its tests in `core/src/test/java`. `web/` wraps the Dropwizard server, while `web-api/` and `client-hc/` expose the HTTP contract. Import utilities live in `reader-gtfs/`, with domain-specific tools under `map-matching/`, `isochrone/`, and `navigation/`. Shared documentation resides in `docs/`, and scripts such as `graphhopper.sh` sit at the repository root. Keep generated data (e.g., `.osm-gh` directories) out of the tree. + +## Build, Test, and Development Commands +Use Maven from the project root: `mvn clean install -DskipTests` compiles all modules and creates `web/target/graphhopper-web-*.jar`. `mvn clean test verify` runs the full test suite and quality checks; run it before every push. For rapid iteration on the server, `mvn -pl web -am package` rebuilds API-facing modules only. During local experiments, `./graphhopper.sh build` packages the web service, and `./graphhopper.sh --action web --config config-example.yml --input berlin-latest.osm.pbf` starts a Dropwizard instance on `localhost:8989`. + +## Coding Style & Naming Conventions +Follow IntelliJ defaults with the shared `.editorconfig`: four-space indentation, Unix line endings, and a 100-character line width. Java classes use PascalCase, fields and methods camelCase, and YAML keys kebab-case (`graphhopper.graph.location`). Prefer descriptive names over abbreviations and avoid reformatting import blocks unless necessary. + +## Testing Guidelines +JUnit-based tests live beside their modules (for example, `core/src/test/java`). New features need unit coverage plus integration coverage when a public API changes; name classes `*Test` or `*IT`. Execute `mvn clean test` locally and add targeted executions (e.g., `mvn -pl map-matching test`) for module-specific work. Include real-world fixtures under `src/test/resources` and keep them minimal. + +## Commit & Pull Request Guidelines +Recent history shows short, imperative commit subjects (e.g., “Set result readonly after writing to it”). Reference relevant issues with `#123` when applicable and limit each commit to one logical change. Pull requests should describe the behaviour change, list validation steps or command outputs, and attach screenshots for UI-facing updates. Confirm `mvn clean test verify` passes before requesting review. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000000..f0b20868ae3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,136 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +GraphHopper is a fast and memory-efficient routing engine written in Java that calculates routes, distances, and turn-by-turn instructions using OpenStreetMap data. It supports multiple routing profiles (car, bike, foot, etc.), advanced algorithms like Contraction Hierarchies (CH) for speed optimization, and provides both a Java library and web service. + +## Development Commands + +### Building and Testing +- **Build entire project**: `mvn clean compile` +- **Run all tests**: `mvn clean test verify` +- **Run single test**: `mvn test -Dtest=TestClassName` +- **Run integration tests**: `mvn verify` +- **Build with dependencies**: `mvn package` + +### Code Quality +- **Run checkstyle**: `mvn checkstyle:check` +- **Run forbidden API checks**: `mvn de.thetaphi:forbiddenapis:check` + +### Running GraphHopper +- **Start web service**: `java -Ddw.graphhopper.datareader.file= -jar web/target/graphhopper-web-*-jar-with-dependencies.jar server config-example.yml` +- **Using shell script**: `./graphhopper.sh -a web -i ` + +### Benchmarking +- **Run benchmarks**: `./benchmark/benchmark.sh ` +- **Download test maps**: `./benchmark/download_map.sh` + +### Individual Module Commands +- **Core module**: `cd core && mvn test` +- **Web module**: `cd web && mvn test` +- **Tools module**: `cd tools && mvn package` + +## High-Level Architecture + +### Module Structure +GraphHopper is organized as a Maven multi-module project: +- **core/**: Main routing engine, algorithms, graph storage, OSM reading +- **web/**: REST API, web service implementation using Dropwizard +- **web-api/**: API models and interfaces +- **client-hc/**: Java HTTP client for GraphHopper API +- **tools/**: Command-line tools, benchmarking, measurement utilities +- **reader-gtfs/**: Public transit routing with GTFS data +- **map-matching/**: GPS trace to road network matching +- **navigation/**: Turn-by-turn navigation features +- **example/**: Sample applications and usage examples + +### Core Components + +#### Graph Storage (`core/src/main/java/com/graphhopper/storage/`) +- **BaseGraph**: Core graph data structure storing nodes and edges +- **GraphHopperStorage**: Main storage implementation with memory-mapped or in-memory options +- **DataAccess**: Low-level data storage abstraction (RAM, memory-mapped) +- **LocationIndex**: Spatial index for finding nearby nodes/edges + +#### Routing Algorithms (`core/src/main/java/com/graphhopper/routing/`) +- **AStar, Dijkstra**: Classic shortest path algorithms with bidirectional variants +- **Contraction Hierarchies (CH)**: Speed optimization preprocessing +- **Landmarks (LM)**: Alternative speed optimization using A* with landmarks +- **EdgeBasedCH**: Turn-aware routing with edge-based graph preprocessing + +#### OSM Data Processing (`core/src/main/java/com/graphhopper/reader/osm/`) +- **OSMReader**: Parses OSM PBF/XML files into graph representation +- **TagParsers**: Extract routing-relevant properties from OSM tags +- **EncodingManager**: Manages vehicle-specific encoded values and parsing + +#### Routing Profiles and Weighting (`core/src/main/java/com/graphhopper/routing/`) +- **Profile**: Defines routing behavior (vehicle + weighting + turn costs) +- **CustomModel**: JSON-based routing customization without code changes +- **Weighting**: Calculates edge costs based on distance, time, preferences + +### Key Design Patterns + +#### Three-Tier Architecture +1. **Import Phase**: OSM data → Graph storage with vehicle-specific encodings +2. **Preprocessing**: Optional speed optimizations (CH/LM preparation) +3. **Query Phase**: Route calculation using prepared graph structures + +#### Encoded Values System +- **EncodedValue**: Stores routing properties per edge (speed, access, surface) +- **BooleanEncodedValue**: Binary properties (access allowed/forbidden) +- **DecimalEncodedValue**: Numeric properties (speed limits, weights) +- **EnumEncodedValue**: Categorical values (road class, surface type) + +#### Flexible vs Speed vs Hybrid Modes +- **Flexible**: No preprocessing, supports dynamic customization +- **Speed**: CH preprocessing, fastest queries, limited customization +- **Hybrid**: LM preprocessing, balanced speed/flexibility + +## Testing Strategy + +### Test Organization +- Unit tests in each module's `src/test/java/` +- Integration tests using `*IT.java` naming convention +- GraphHopper uses JUnit 5 for all testing +- Test data in `core/files/` directory + +### Key Test Patterns +- **AlgoTester**: Framework for testing routing algorithms with various graph configurations +- **GHUtility**: Test utilities for creating simple test graphs +- **Measurement**: Performance benchmarking and regression testing + +## Configuration + +### Primary Config File: `config-example.yml` +- **Profiles**: Define vehicle types and routing behavior +- **CH/LM**: Speed optimization settings +- **Import**: OSM data processing options +- **Web**: Server configuration for REST API + +### Common Development Settings +- **Memory**: Adjust JVM heap size based on map size (use `-Xmx` flag) +- **Data Access**: Choose between RAM_STORE (fast) or MMAP (memory-efficient) +- **Profiles**: Enable/disable vehicle types and customization options + +## Important Development Notes + +### Code Style +- Java 8+ required +- 4-space indentation, 100-character line limit +- IntelliJ IDEA defaults, EditorConfig support +- Unix line endings (LF) +- Extensive test coverage expected + +### Performance Considerations +- Graph preprocessing (CH/LM) trades memory/startup time for query speed +- Memory-mapped storage enables handling large maps with limited RAM +- Encoded values system allows efficient storage of routing properties +- Contraction and landmarks dramatically improve routing performance + +### Common Customization Points +- **Custom Models**: Modify routing behavior via JSON without code changes +- **Tag Parsers**: Extract custom properties from OSM data +- **Weighting**: Implement custom cost functions +- **Encoded Values**: Add new edge properties for routing decisions \ No newline at end of file diff --git a/core/src/main/java/com/graphhopper/GraphHopper.java b/core/src/main/java/com/graphhopper/GraphHopper.java index 49c02d1e8e1..f1d47afd5f4 100644 --- a/core/src/main/java/com/graphhopper/GraphHopper.java +++ b/core/src/main/java/com/graphhopper/GraphHopper.java @@ -23,7 +23,8 @@ import com.graphhopper.config.LMProfile; import com.graphhopper.config.Profile; import com.graphhopper.jackson.Jackson; -import com.graphhopper.reader.dem.*; +import com.graphhopper.reader.dem.EdgeElevationInterpolator; +import com.graphhopper.reader.dem.ElevationProvider; import com.graphhopper.reader.osm.OSMReader; import com.graphhopper.reader.osm.RestrictionTagParser; import com.graphhopper.reader.osm.conditional.DateRangeParser; @@ -95,6 +96,7 @@ public class GraphHopper { private final LinkedHashMap dataAccessConfig = new LinkedHashMap<>(); private boolean sortGraph = false; private boolean elevation = false; + private boolean elevationEdgeInterpolationEnabled = true; private LockFactory lockFactory = new NativeFSLockFactory(); private boolean allowWrites = true; private boolean fullyLoaded = false; @@ -185,6 +187,16 @@ public GraphHopper setElevationProvider(ElevationProvider eleProvider) { return this; } + public GraphHopper setElevationEdgeInterpolationEnabled(boolean enabled) { + ensureNotLoaded(); + this.elevationEdgeInterpolationEnabled = enabled; + return this; + } + + public boolean isElevationEdgeInterpolationEnabled() { + return elevationEdgeInterpolationEnabled; + } + public GraphHopper setPathDetailsBuilderFactory(PathDetailsBuilderFactory pathBuilderFactory) { this.pathBuilderFactory = pathBuilderFactory; return this; @@ -577,6 +589,7 @@ public GraphHopper init(GraphHopperConfig ghConfig) { osmReaderConfig.setElevationSmoothingRamerMax(ghConfig.getInt("graph.elevation.edge_smoothing.ramer.max_elevation", osmReaderConfig.getElevationSmoothingRamerMax())); osmReaderConfig.setLongEdgeSamplingDistance(ghConfig.getDouble("graph.elevation.long_edge_sampling_distance", osmReaderConfig.getLongEdgeSamplingDistance())); osmReaderConfig.setElevationMaxWayPointDistance(ghConfig.getDouble("graph.elevation.way_point_max_distance", osmReaderConfig.getElevationMaxWayPointDistance())); + elevationEdgeInterpolationEnabled = ghConfig.getBool("graph.elevation.edge_interpolation", elevationEdgeInterpolationEnabled); routerConfig.setElevationWayPointMaxDistance(ghConfig.getDouble("graph.elevation.way_point_max_distance", routerConfig.getElevationWayPointMaxDistance())); ElevationProvider elevationProvider = createElevationProvider(ghConfig); setElevationProvider(elevationProvider); @@ -762,56 +775,7 @@ public static Map getVehiclesByName(String vehiclesStr, Collecti } private static ElevationProvider createElevationProvider(GraphHopperConfig ghConfig) { - String eleProviderStr = toLowerCase(ghConfig.getString("graph.elevation.provider", "noop")); - - if (ghConfig.has("graph.elevation.calcmean")) - throw new IllegalArgumentException("graph.elevation.calcmean is deprecated, use graph.elevation.interpolate"); - - String cacheDirStr = ghConfig.getString("graph.elevation.cache_dir", ""); - if (cacheDirStr.isEmpty() && ghConfig.has("graph.elevation.cachedir")) - throw new IllegalArgumentException("use graph.elevation.cache_dir not cachedir in configuration"); - - ElevationProvider elevationProvider = ElevationProvider.NOOP; - if (eleProviderStr.equalsIgnoreCase("hgt")) { - elevationProvider = new HGTProvider(cacheDirStr); - } else if (eleProviderStr.equalsIgnoreCase("srtm")) { - elevationProvider = new SRTMProvider(cacheDirStr); - } else if (eleProviderStr.equalsIgnoreCase("cgiar")) { - elevationProvider = new CGIARProvider(cacheDirStr); - } else if (eleProviderStr.equalsIgnoreCase("gmted")) { - elevationProvider = new GMTEDProvider(cacheDirStr); - } else if (eleProviderStr.equalsIgnoreCase("srtmgl1")) { - elevationProvider = new SRTMGL1Provider(cacheDirStr); - } else if (eleProviderStr.equalsIgnoreCase("multi")) { - elevationProvider = new MultiSourceElevationProvider(cacheDirStr); - } else if (eleProviderStr.equalsIgnoreCase("skadi")) { - elevationProvider = new SkadiProvider(cacheDirStr); - } - - if (elevationProvider instanceof TileBasedElevationProvider) { - TileBasedElevationProvider provider = (TileBasedElevationProvider) elevationProvider; - - String baseURL = ghConfig.getString("graph.elevation.base_url", ""); - if (baseURL.isEmpty() && ghConfig.has("graph.elevation.baseurl")) - throw new IllegalArgumentException("use graph.elevation.base_url not baseurl in configuration"); - - DAType elevationDAType = DAType.fromString(ghConfig.getString("graph.elevation.dataaccess", "MMAP")); - - boolean interpolate = ghConfig.has("graph.elevation.interpolate") - ? "bilinear".equals(ghConfig.getString("graph.elevation.interpolate", "none")) - : ghConfig.getBool("graph.elevation.calc_mean", false); - - boolean removeTempElevationFiles = ghConfig.getBool("graph.elevation.cgiar.clear", true); - removeTempElevationFiles = ghConfig.getBool("graph.elevation.clear", removeTempElevationFiles); - - provider - .setAutoRemoveTemporaryFiles(removeTempElevationFiles) - .setInterpolate(interpolate) - .setDAType(elevationDAType); - if (!baseURL.isEmpty()) - provider.setBaseURL(baseURL); - } - return elevationProvider; + return ElevationProvider.NOOP; } private void printInfo() { @@ -1241,6 +1205,10 @@ protected void importPublicTransit() { } void interpolateBridgesTunnelsAndFerries() { + // Use configuration to skip this step when elevations are already preprocessed (e.g. iOS build pipeline). + if (!elevationEdgeInterpolationEnabled) + return; + if (encodingManager.hasEncodedValue(RoadEnvironment.KEY)) { EnumEncodedValue roadEnvEnc = encodingManager.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class); StopWatch sw = new StopWatch().start(); @@ -1250,10 +1218,11 @@ void interpolateBridgesTunnelsAndFerries() { new EdgeElevationInterpolator(baseGraph.getBaseGraph(), roadEnvEnc, RoadEnvironment.BRIDGE).execute(); float bridge = sw.stop().getSeconds(); // The SkadiProvider contains bathymetric data. For ferries this can result in bigger elevation changes - // See #2098 for mor information + // See #2098 for more information sw = new StopWatch().start(); new EdgeElevationInterpolator(baseGraph.getBaseGraph(), roadEnvEnc, RoadEnvironment.FERRY).execute(); - logger.info("Bridge interpolation " + (int) bridge + "s, " + "tunnel interpolation " + (int) tunnel + "s, ferry interpolation " + (int) sw.stop().getSeconds() + "s"); + logger.info("Bridge interpolation {}s, tunnel interpolation {}s, ferry interpolation {}s", + (int) bridge, (int) tunnel, (int) sw.stop().getSeconds()); } } diff --git a/core/src/main/java/com/graphhopper/routing/ev/EncodedValueSerializer.java b/core/src/main/java/com/graphhopper/routing/ev/EncodedValueSerializer.java index 82d5bd6185e..fd30efe56eb 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/EncodedValueSerializer.java +++ b/core/src/main/java/com/graphhopper/routing/ev/EncodedValueSerializer.java @@ -23,7 +23,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; public class EncodedValueSerializer { private final static ObjectMapper MAPPER = new ObjectMapper(); @@ -31,7 +31,7 @@ public class EncodedValueSerializer { static { MAPPER.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); MAPPER.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); - MAPPER.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + MAPPER.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); } public static String serializeEncodedValue(EncodedValue encodedValue) { diff --git a/web-api/src/main/java/com/graphhopper/jackson/Jackson.java b/web-api/src/main/java/com/graphhopper/jackson/Jackson.java index 0cc8b58c413..36dba40780e 100644 --- a/web-api/src/main/java/com/graphhopper/jackson/Jackson.java +++ b/web-api/src/main/java/com/graphhopper/jackson/Jackson.java @@ -20,7 +20,7 @@ import com.bedatadriven.jackson.datatype.jts.JtsModule; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; public class Jackson { @@ -31,7 +31,7 @@ public static ObjectMapper newObjectMapper() { public static ObjectMapper initObjectMapper(ObjectMapper objectMapper) { objectMapper.registerModule(new GraphHopperModule()); objectMapper.registerModule(new JtsModule()); - objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); return objectMapper; }