diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 74d2b4e..0f91e86 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -2,9 +2,9 @@ name: Build and test on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main" ] + branches: ["main"] permissions: contents: write @@ -20,8 +20,8 @@ jobs: - name: Set up JDK 21 uses: actions/setup-java@v5 with: - java-version: '21' - distribution: 'temurin' + java-version: "21" + distribution: "temurin" - name: Set up Gradle uses: gradle/actions/setup-gradle@v5 diff --git a/.github/workflows/Release.yaml b/.github/workflows/Release.yaml index 64165ad..98ccc4b 100644 --- a/.github/workflows/Release.yaml +++ b/.github/workflows/Release.yaml @@ -7,6 +7,10 @@ on: permissions: contents: read +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + jobs: release: runs-on: ubuntu-latest @@ -22,8 +26,8 @@ jobs: - name: Set up JDK 21 uses: actions/setup-java@v5 with: - java-version: '21' - distribution: 'temurin' + java-version: "21" + distribution: "temurin" - name: Set up Gradle uses: gradle/actions/setup-gradle@v5 @@ -35,7 +39,9 @@ jobs: - name: Extract version from tag id: version + shell: bash run: | + set -euo pipefail # Remove 'v' prefix if present (v0.2.3 -> 0.2.3) VERSION="${GITHUB_REF_NAME#v}" echo "VERSION=$VERSION" >> $GITHUB_OUTPUT diff --git a/.github/workflows/UpdateReadmeVersion.yaml b/.github/workflows/UpdateReadmeVersion.yaml new file mode 100644 index 0000000..041545b --- /dev/null +++ b/.github/workflows/UpdateReadmeVersion.yaml @@ -0,0 +1,115 @@ +name: Update README version (Maven Central) + +on: + # Keep README correct even if Central metadata lags a bit after release + schedule: + - cron: "23 6 * * *" # daily + workflow_dispatch: {} + # Best-effort: Central may lag behind a published GitHub release + release: + types: [published] + +permissions: + contents: write + +concurrency: + group: update-readme-version + cancel-in-progress: false + +jobs: + update-readme: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Resolve latest release version from Maven Central metadata + id: resolve + shell: bash + run: | + set -euo pipefail + + GROUP_ID="com.williamcallahan" + ARTIFACT_ID="apple-maps-java" + + METADATA_URL="https://repo1.maven.org/maven2/${GROUP_ID//.//}/${ARTIFACT_ID}/maven-metadata.xml" + + echo "Fetching: ${METADATA_URL}" + XML="$(curl -fsSL "$METADATA_URL")" + + # Extract ... (fallback to if needed) + RELEASE="$(printf '%s' "$XML" | sed -n 's:.*\(.*\).*:\1:p' | head -n 1 || true)" + if [[ -z "${RELEASE}" ]]; then + RELEASE="$(printf '%s' "$XML" | sed -n 's:.*\(.*\).*:\1:p' | head -n 1 || true)" + fi + + if [[ -z "${RELEASE}" ]]; then + echo "Failed to resolve release version from maven-metadata.xml" + exit 1 + fi + + # Guardrail: don't ever write SNAPSHOT into README install snippets + if [[ "${RELEASE}" == *SNAPSHOT* ]]; then + echo "Resolved version is a SNAPSHOT (${RELEASE}); refusing to update README." + exit 1 + fi + + echo "version=${RELEASE}" >> "$GITHUB_OUTPUT" + echo "Resolved release version: ${RELEASE}" + + - name: Update README.md snippets + shell: bash + run: | + set -euo pipefail + + VERSION='${{ steps.resolve.outputs.version }}' + + if [[ ! -f "README.md" ]]; then + echo "README.md not found at repo root" + exit 1 + fi + + # Replace only the dependency coordinates/version lines in the documented snippets. + # This avoids rewriting unrelated occurrences of old versions elsewhere. + perl -0777 -i -pe ' + my $v = $ENV{VERSION}; + + # Gradle snippet: implementation("group:artifact:VERSION") + s/(implementation\(\"com\.williamcallahan:apple-maps-java:)[^\"\)]+(\"\))/$1$v$2/g; + + # Maven snippet: VERSION within the apple-maps-java dependency block + s#(\s*com\.williamcallahan\s*apple-maps-java\s*)[^<]+(\s*)#$1$v$2#gms; + + ' README.md + + echo "README.md updated to version ${VERSION}" + + env: + VERSION: ${{ steps.resolve.outputs.version }} + + - name: Detect changes + id: diff + shell: bash + run: | + set -euo pipefail + if git diff --quiet -- README.md; then + echo "changed=false" >> "$GITHUB_OUTPUT" + echo "No changes to commit." + else + echo "changed=true" >> "$GITHUB_OUTPUT" + echo "README.md changed." + git --no-pager diff -- README.md + fi + + - name: Commit and push + if: steps.diff.outputs.changed == 'true' + shell: bash + run: | + set -euo pipefail + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + git add README.md + git commit -m "docs(readme): update dependency version to ${{ steps.resolve.outputs.version }}" + git push diff --git a/README.md b/README.md index a83554f..9f2e23d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,28 @@ -# Apple Maps SDK for Java (Server) +[![Maven Central](https://img.shields.io/maven-central/v/com.williamcallahan/apple-maps-java)](https://central.sonatype.com/artifact/com.williamcallahan/apple-maps-java) +[![CI](https://github.com/WilliamAGH/apple-maps-java/actions/workflows/CI.yaml/badge.svg)](https://github.com/WilliamAGH/apple-maps-java/actions/workflows/CI.yaml) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE.md) +[![Context7](src/main/resources/static/img/context7-badge.svg)](https://context7.com/williamagh/apple-maps-java) +[![DeepWiki](src/main/resources/static/img/deepwiki-badge.svg)](https://deepwiki.com/WilliamAGH/apple-maps-java) -A lightweight Java SDK for the Apple Maps Server API, with automatic access-token exchange/refresh. +# Apple Maps Server SDK for Java + +A lightweight, unofficial Java SDK for the [Apple Maps Server API](https://developer.apple.com/documentation/applemapsserverapi). +Designed for backend/JVM apps that need server-side geocoding, search, directions, or ETA via a REST API. + +This SDK automatically exchanges your long-lived Apple-issued authorization token (JWT) for short-lived access tokens and refreshes as needed. + +Note: this is **not** [MapKit](https://developer.apple.com/documentation/mapkit) (native UI SDK) or [MapKit JS](https://developer.apple.com/documentation/mapkitjs) (web UI SDK). +This project is not affiliated with Apple. + +## Apple Maps ecosystem + +Apple provides three primary ways to integrate Maps. This library supports #1 (Server API). + +| Service | Purpose | Best for | +| :--- | :--- | :--- | +| [Apple Maps Server API](https://developer.apple.com/documentation/applemapsserverapi) | REST HTTP API | Backend services (Java/Kotlin/Python/etc.) that need geocoding, search, directions, or ETA data without a UI. | +| [MapKit](https://developer.apple.com/documentation/mapkit) | Native UI framework | iOS/macOS apps that need interactive maps. | +| [MapKit JS](https://developer.apple.com/documentation/mapkitjs) | JavaScript UI library | Web apps that need interactive maps in the browser. | ## Requirements @@ -10,15 +32,13 @@ A lightweight Java SDK for the Apple Maps Server API, with automatic access-toke ## Installation -Replace `0.1.3` with the latest release. - -Note: this repo’s build uses a Gradle Java toolchain (Java 17). If you don’t have JDK 17 installed locally, Gradle will download it automatically. +This repo’s build uses a Gradle Java toolchain (Java 17). If you don’t have JDK 17 installed locally, Gradle will download it automatically. ### Gradle ```groovy dependencies { - implementation("com.williamcallahan:apple-maps-java:0.1.3") + implementation("com.williamcallahan:apple-maps-java:0.1.5") } ``` @@ -28,11 +48,13 @@ dependencies { com.williamcallahan apple-maps-java - 0.1.3 + 0.1.5 ``` -## Configure `APPLE_MAPS_TOKEN` +## Configuration + +### `APPLE_MAPS_TOKEN` (required) Option A (recommended): environment variable @@ -54,6 +76,14 @@ cp .env-example .env Then set `APPLE_MAPS_TOKEN=...` in `.env`. This repo’s Gradle build loads `.env` into system properties (useful for running integration tests locally). `.env` is ignored by git. +### `APPLE_MAPS_ORIGIN` (optional) + +If your authorization token (JWT) was generated with a specific `origin` claim, set `APPLE_MAPS_ORIGIN` as well (this value is sent as the HTTP `Origin` header): + +```bash +export APPLE_MAPS_ORIGIN="https://api.example.com" +``` + ## Verify your token (integration test) Run the live integration test suite: @@ -75,8 +105,10 @@ Or run a one-off CLI query: ``` More: +- Authorization/token details: `docs/authorization.md` - Integration tests: `docs/tests.md` - CLI usage: `docs/cli.md` +- More examples: `docs/usage.md` ## Example: geocode Startup HQ (San Francisco) @@ -89,7 +121,14 @@ if (token == null || token.isBlank()) { throw new IllegalStateException("Set APPLE_MAPS_TOKEN (env var) or APPLE_MAPS_TOKEN (system property)."); } -AppleMaps api = new AppleMaps(token); +String origin = System.getenv("APPLE_MAPS_ORIGIN"); +if (origin == null || origin.isBlank()) { + origin = System.getProperty("APPLE_MAPS_ORIGIN"); +} + +AppleMaps api = (origin == null || origin.isBlank()) + ? new AppleMaps(token) + : new AppleMaps(token, origin); PlaceResults results = api.geocode(GeocodeInput.builder("880 Harrison St, San Francisco, CA 94107").build()); System.out.println(results); ``` @@ -129,13 +168,15 @@ Example response (trimmed): } ``` -## What’s included +## Supported Server API features -- Geocode + reverse geocode -- Search + autocomplete (and `resolveCompletionUrls`) -- Directions + ETA -- Place lookup + alternate IDs -More examples: `docs/usage.md` +This SDK provides typed wrappers for common Apple Maps Server API operations: + +- **Geocoding**: `geocode` and `reverseGeocode` +- **Search**: `search` +- **Autocomplete**: `autocomplete`, `resolveCompletionUrl`, and `resolveCompletionUrls` +- **Directions & ETA**: `directions` and `etas` +- **Places**: `lookupPlace`, `lookupPlaces`, and `lookupAlternateIds` Common use case: business / startup search (name-only or name + address) via Search + Autocomplete. @@ -149,7 +190,15 @@ Common use case: business / startup search (name-only or name + address) via Sea ## Quota (included limits) -Apple provides daily quotas per Apple Developer Program membership (for example, a service-call limit shared between MapKit JS service requests and Apple Maps Server API calls). When you exceed the daily service-call quota, Apple responds with HTTP `429 Too Many Requests`. Apple does not provide a self-serve way to purchase additional quota; for extra capacity, contact Apple via the Maps developer dashboard. https://maps.developer.apple.com/ +Apple provides free daily quotas that are currently significantly more generous than Google Maps API. But you must have a paid Apple Developer Program membership, which costs $99/year for access to all its Apple ecosystem resources. This API is Apple hardware/software agnostic. + +Apple applies a daily service-call limit per membership (for example, a quota shared between MapKit JS service requests and Apple Maps Server API calls). When you exceed the daily quota, Apple responds with HTTP `429 Too Many Requests`. Apple does not provide a self-serve way to purchase additional quota; for extra capacity, contact Apple via the [Maps developer dashboard](https://maps.developer.apple.com/). + +## Built with Apple Maps Java + +[![Brief with Apple Maps](src/main/resources/static/img/apple-maps-java-screenshot.png)](https://github.com/WilliamAGH/brief) + +**[Brief](https://github.com/WilliamAGH/brief)** - Terminal AI chat client with `/locate` command powered by Apple Maps Java and rendered with [TUI4J](https://github.com/WilliamAGH/tui4j). ## More from the author @@ -157,3 +206,4 @@ Apple provides daily quotas per Apple Developer Program membership (for example, - [TUI4J](https://github.com/WilliamAGH/tui4j) (a modern TUI library for Java) - [Brief](https://github.com/WilliamAGH/brief) (an open-source terminal AI chat app in Java) +- [Other projects](https://williamcallahan.com/projects) diff --git a/build.gradle.kts b/build.gradle.kts index 79dd5e6..da64af4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -47,7 +47,7 @@ val javaTargetVersion = 17 group = providers.gradleProperty("GROUP").orNull ?: "com.williamcallahan" version = providers.gradleProperty("version").orNull ?: providers.gradleProperty("VERSION_NAME").orNull - ?: "0.1.4-SNAPSHOT" + ?: "0.1.5" repositories { mavenCentral() diff --git a/gradle.properties b/gradle.properties index 5ffe9e4..22fea59 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ GROUP=com.williamcallahan POM_ARTIFACT_ID=apple-maps-java -VERSION_NAME=0.1.4-SNAPSHOT +VERSION_NAME=0.1.5 POM_NAME=Apple Maps Java POM_DESCRIPTION=Apple Maps Java implements the Apple Maps Server API for use in JVMs. diff --git a/src/main/resources/static/img/apple-maps-java-screenshot.png b/src/main/resources/static/img/apple-maps-java-screenshot.png new file mode 100644 index 0000000..e960496 Binary files /dev/null and b/src/main/resources/static/img/apple-maps-java-screenshot.png differ diff --git a/src/main/resources/static/img/context7-badge.svg b/src/main/resources/static/img/context7-badge.svg new file mode 100644 index 0000000..28c8c49 --- /dev/null +++ b/src/main/resources/static/img/context7-badge.svg @@ -0,0 +1 @@ +Context7 MCP: IndexedContext7 MCPIndexed \ No newline at end of file diff --git a/src/main/resources/static/img/deepwiki-badge.svg b/src/main/resources/static/img/deepwiki-badge.svg new file mode 100644 index 0000000..95548d0 --- /dev/null +++ b/src/main/resources/static/img/deepwiki-badge.svg @@ -0,0 +1 @@ +DeepWiki: WilliamAGH/apple-maps-javaDeepWikiWilliamAGH/apple-maps-java \ No newline at end of file