-
Notifications
You must be signed in to change notification settings - Fork 7
Add geo module (offline + nominatim) #17
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
base: main
Are you sure you want to change the base?
Changes from all commits
fcc1de6
a63fa63
10e4b03
cd8f130
1b8baee
e5d38eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| name: Build and Deploy AlpsLib-Geo | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| push: | ||
| branches: [ main ] | ||
| paths: | ||
| - 'geo/build.gradle.kts' | ||
| # - 'geo/**' | ||
| # - '.github/workflows/deploy-geo.yml' | ||
| # - 'gradle/**' | ||
| # - 'buildSrc/**' | ||
|
|
||
| jobs: | ||
| build-and-deploy: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v5 | ||
|
|
||
| - name: Set up JDK | ||
| uses: actions/setup-java@v5 | ||
| with: | ||
| distribution: 'temurin' | ||
| java-version: '21' | ||
| cache: 'gradle' | ||
|
|
||
| - name: Setup Gradle | ||
| uses: gradle/actions/setup-gradle@v5 | ||
|
|
||
| - name: Build with Gradle | ||
| run: ./gradlew clean :alpslib-geo:build | ||
|
|
||
| # The USERNAME and TOKEN need to correspond to the credentials environment variables used in | ||
| # the publishing section of your build.gradle | ||
| - name: Publish to GitHub Packages | ||
| run: ./gradlew :alpslib-geo:publish | ||
| env: | ||
| ORG_GRADLE_PROJECT_alpsMavenUser: ${{ secrets.MAVEN_USERNAME }} | ||
| ORG_GRADLE_PROJECT_alpsMavenPassword: ${{ secrets.MAVEN_PASSWORD }} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| This module can be used to get geo information (like country or city) of a location identified by latitude and longitude. This is possible both offline and over https using Nominatim. | ||
|
|
||
| # Retrieving data | ||
|
|
||
| ## Offline ([ReverseGeocoder](https://github.com/kno10/reversegeocode)) | ||
|
|
||
| First you need to create a RgcHandler. It utilizes the [ReverseGeocoder](https://github.com/kno10/reversegeocode) class by kno10. You need to provide an OSM data file (find them in [their repo](https://github.com/kno10/reversegeocode/tree/master/data)). The most accurate results can be achieved using files with high resolutions. | ||
|
|
||
| Using the RgcHandlers method "locationFromCoordinates" you can get a RgcGeoLocation with all information the ReverseGeocoder found. | ||
|
|
||
| Access it by using the "get" Method. You need to provide an AdminLevel (find more information below). This will use the AdminLevels Integer "level" property. | ||
|
|
||
| ## [Nominatim](https://nominatim.org/release-docs/develop/api/Reverse/) | ||
|
|
||
| First you need to create a NominatimHandler. It utilizes the Nominatim reverse API to access OSM data. Because of that you need to provide your applications userAgent for the http requests. | ||
|
|
||
| Using the NominatimHandlers method "locationFromCoordinates" you can get a NominatimGeoLocation with all information of the "address" JSON object of the Nominatim response. | ||
|
|
||
| Access it by using the "get" Method. You need to provide an AdminLevel (find more information below). This will use the AdminLevels String "nominatimKey" property. | ||
|
|
||
| # AdminLevel | ||
|
|
||
| AdminLevels provide a constant way to access the parts of the queried data that you want to use. For example, you might want to define an AdminLevel for States in Germany: | ||
|
|
||
| ``` | ||
| public enum DEAdminLevel implements AdminLevel { | ||
|
|
||
| STATE(4); | ||
|
|
||
| private final int level; | ||
|
|
||
| DEAdminLevel(int level) { | ||
| this.level = level; | ||
| } | ||
|
|
||
| @Override | ||
| public Integer getLevel() { | ||
| return this.level; | ||
| } | ||
|
|
||
| @Override | ||
| public String getNominatimKey() { | ||
| return null; | ||
| } | ||
|
|
||
| } | ||
| ``` | ||
|
|
||
| For Rgc: An AdminLevels "level" refers to [OSM admin_levels](https://wiki.openstreetmap.org/wiki/Tag:boundary%3Dadministrative). As you can see, their meaning is not the same for every country. | ||
|
|
||
| For Nominatim: An AdminLevels "nominatimKey" refers to the keys used for the data in the "address" JSON response of a [Nominatim request](https://nominatim.org/release-docs/develop/api/Reverse/). | ||
|
|
||
| Both "level" and "nominatimKey" are allowed to be null as some AdminLevels only exist for RgcGeoLocations and others for NominationGeoLocations. | ||
|
|
||
| The "AdminLevel.COUNTRY" is the only one implemented by default as it is the same for all countries. | ||
|
|
||
| # Other peoples work used | ||
|
|
||
| - OSM (OpenStreetMap) data: © OpenStreetMap contributors | ||
| - [ReverseGeocoder](https://github.com/kno10/reversegeocode): Copyright (c) 2015, Erich Schubert |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| plugins { | ||
| id("buildlogic.java-conventions") | ||
| alias(libs.plugins.freefair.lombok) | ||
| } | ||
|
|
||
| dependencies { | ||
| compileOnly(libs.org.slf4j.slf4j.api) | ||
| compileOnly(libs.org.json.json) | ||
| } | ||
|
|
||
| description = "AlpsLib-Geo" | ||
| version = "1.0.0" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package com.alpsbte.alpslib.geo; | ||
|
|
||
| /** | ||
| * Implementations represent either an OSM administrative level, a Nominatim address key or both for accessing parts of data, e.g. the country or city of a location | ||
| * @see <a href="https://wiki.openstreetmap.org/wiki/Tag:boundary%3Dadministrative">OSM administrative levels</a> | ||
| * @see <a href="https://nominatim.org/release-docs/develop/api/Reverse/">Nominatim documentation</a> | ||
| */ | ||
| public interface AdminLevel { | ||
|
|
||
| /** | ||
| * AdminLevel to provide access to country data. This is the only admin level that is the same for all countries. | ||
| */ | ||
| AdminLevel COUNTRY = AdminLevel.of(2, "country"); | ||
|
|
||
| /** | ||
| * Get a simple AdminLevel without having to implement the interface manually | ||
| * @param level The numeric admin level if this AdminLevel is used for a {@link com.alpsbte.alpslib.geo.rgc.RgcGeoLocation}. null otherwise | ||
| * @param nominatimKey The key for the corresponding data to get from the Nominatim responses "address" JSON object if this AdminLevel is used for a {@link com.alpsbte.alpslib.geo.nominatim.NominatimGeoLocation}. null otherwise | ||
| * @return The AdminLevel with level and nominatimKey | ||
| */ | ||
| static AdminLevel of(Integer level, String nominatimKey) { | ||
| return new AdminLevel() { | ||
| @Override | ||
| public Integer getLevel() { | ||
| return level; | ||
| } | ||
|
|
||
| @Override | ||
| public String getNominatimKey() { | ||
| return nominatimKey; | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * The numeric admin level | ||
| * @return The numeric admin level or null if it is not set | ||
| * @see <a href="https://wiki.openstreetmap.org/wiki/Tag:boundary%3Dadministrative">OSM administrative levels</a> | ||
| */ | ||
| Integer getLevel(); | ||
|
|
||
| /** | ||
| * The key for the corresponding data to get from the Nominatim responses "address" JSON object | ||
| * @return The key for the corresponding data to get from the Nominatim responses "address" JSON object | ||
| * @see <a href="https://nominatim.org/release-docs/develop/api/Reverse/">Nominatim documentation</a> | ||
| */ | ||
| String getNominatimKey(); | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.alpsbte.alpslib.geo; | ||
|
|
||
| /** | ||
| * Holds data of a location that can be accessed using {@link AdminLevel}s. | ||
| */ | ||
| public interface GeoLocation { | ||
|
|
||
| /** | ||
| * Access a locations data value by an {@link AdminLevel} | ||
| * @param adminLevel Defines the data key | ||
| * @return The value for the key. Or null if the key is null or there is no value for the key in the data | ||
| */ | ||
| String get(AdminLevel adminLevel); | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package com.alpsbte.alpslib.geo.nominatim; | ||
|
|
||
| import com.alpsbte.alpslib.geo.AdminLevel; | ||
| import com.alpsbte.alpslib.geo.GeoLocation; | ||
|
|
||
| import java.util.Map; | ||
|
|
||
| /** | ||
| * Represents a location with data from Nominatim rgc | ||
| * @param latitude The locations latitude | ||
| * @param longitude The locations longitude | ||
| * @param adminLevelsValues The Nominatim data with {@link AdminLevel#getNominatimKey()} values as keys | ||
| */ | ||
| public record NominatimGeoLocation(double latitude, double longitude, Map<String, String> adminLevelsValues) implements GeoLocation { | ||
|
|
||
| @Override | ||
| public String get(AdminLevel adminLevel) { | ||
| if (adminLevel.getNominatimKey() == null) { | ||
| return null; | ||
| } | ||
|
|
||
| return this.adminLevelsValues.get(adminLevel.getNominatimKey()); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| package com.alpsbte.alpslib.geo.nominatim; | ||
|
|
||
|
|
||
| import org.json.JSONObject; | ||
| import org.slf4j.Logger; | ||
|
|
||
| import java.io.BufferedReader; | ||
| import java.io.IOException; | ||
| import java.io.InputStreamReader; | ||
| import java.net.HttpURLConnection; | ||
| import java.net.URI; | ||
| import java.net.URL; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| /** | ||
| * Provides access to retrieving location data from Nominatim (OSM). | ||
| * @see <a href="https://nominatim.org/release-docs/develop/api/Reverse/">Nominatim documentation</a> | ||
| */ | ||
| public class NominatimHandler { | ||
|
|
||
| private final Logger logger; | ||
| private final String userAgent; | ||
| private final int zoom; | ||
|
|
||
| /** | ||
| * Create a NominatimHandler object with a default zoom of 10 | ||
| * @param logger The logger to use | ||
| * @param userAgent Your application's User Agent for the Nominatim http request. | ||
| */ | ||
| public NominatimHandler(Logger logger, String userAgent) { | ||
| this(logger, userAgent, 10); | ||
| } | ||
|
|
||
| /** | ||
| * Create a NominatimHandler object | ||
| * @param logger The logger to use | ||
| * @param userAgent Your application's User Agent for the Nominatim http request. | ||
| * @param zoom The zoom for the returned data's detail | ||
| */ | ||
| public NominatimHandler(Logger logger, String userAgent, int zoom) { | ||
| this.logger = logger; | ||
| this.userAgent = userAgent; | ||
| this.zoom = zoom; | ||
| } | ||
|
|
||
| /** | ||
| * Get location data for coordinates | ||
| * @param latitude The locations latitude | ||
| * @param longitude The locations longitude | ||
| * @return The location data or null if there were errors | ||
| */ | ||
| public NominatimGeoLocation locationFromCoordinates(float latitude, float longitude) { | ||
| try { | ||
| JSONObject response; | ||
|
|
||
| URL url = URI.create("https://nominatim.openstreetmap.org/reverse?lat=" + latitude + "&lon=" + longitude + "&format=json&zoom=" + this.zoom).toURL(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you make that komoot compatible? People might want to use that for obvious reasons |
||
| HttpURLConnection con = (HttpURLConnection) url.openConnection(); | ||
| con.setRequestProperty("User-Agent", this.userAgent); | ||
| con.setRequestProperty("Accept", "application/json"); | ||
|
|
||
| BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8)); | ||
| StringBuilder builder = new StringBuilder(); | ||
| reader.lines().forEach(builder::append); | ||
| response = new JSONObject(builder.toString()); | ||
| con.disconnect(); | ||
|
|
||
| if (!response.has("address")) { | ||
| this.logger.warn("Invalid nominatim response: %s".formatted(response.toString())); | ||
| return null; | ||
| } | ||
|
|
||
| JSONObject address = response.getJSONObject("address"); | ||
|
|
||
| Map<String, String> locValues = new HashMap<>(); | ||
| for (String key : address.keySet()) { | ||
| locValues.put(key, address.getString(key)); | ||
| } | ||
|
|
||
| return new NominatimGeoLocation(latitude, longitude, locValues); | ||
|
|
||
| } catch (IOException e) { | ||
| this.logger.error("Error nominatim", e); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please include a link to the readme of geocode