Skip to content

About

A JOSM Plugin to script with Python3 via GraalPy with full JOSM data and API access, replacing Jython2.7 by the `josm-scripting-plugin`

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

JOSM GraalPy PoC

Proof-of-concept JOSM plugin that evals Python 3 via GraalPy (Polyglot API) and can read/write JOSM objects (DataSet, LayerManager, MainApplication). Scripts can import numpy.

This is not a drop-in replacement for the JOSM Scripting plugin. GraalPy language JARs are not packed into graalpy.jar (that would strip Truffle’s META-INF/ and the engine would not start). They ship next to it as graalpy-libs/, listed on the plugin manifest Class-Path. JOSM puts those URLs on PluginClassLoader. ./gradlew runJosm also puts them on java.class.path.

Install

install.sh copies graalpy.jar and graalpy-libs/ into JOSM's default plugins directory. The plugin jar must keep that name — JOSM keys plugins off the filename. Language JARs stay in the subdirectory so JOSM does not treat them as plugins.

# After a local build (./gradlew dist → build/dist/graalpy.jar + graalpy-libs/)
./install.sh

# A GitHub release zip (graalpy.jar + graalpy-libs/ in the same folder)
unzip josm-graalpy-0.1.0.zip -d graalpy-release
./install.sh graalpy-release/graalpy.jar

The script follows JOSM's own user-data rule:

OS Plugins directory
Linux ~/.josm/plugins if that legacy home still exists, otherwise ${XDG_DATA_HOME:-~/.local/share}/JOSM/plugins
macOS ~/Library/JOSM/plugins
Windows %APPDATA%\JOSM\plugins

Override with --dir DIR or JOSM_PLUGIN_DIR. --libs DIR overrides the language-JAR folder. If the plugins directory (or the JOSM user-data home around it) does not exist, the script warns — JOSM has probably never been started on this account — then creates the directory and copies anyway.

Restart JOSM and enable graalpy under Edit → Preferences → Plugins on the first install. Stock java -jar josm.jar on JDK 25 needs native access for NumPy:

java --enable-native-access=ALL-UNNAMED -jar josm.jar

Build

The Gradle daemon stays on JDK 21 (gradle/gradle-daemon-jvm.properties) because Gradle 8.10.2 cannot evaluate Kotlin DSL on JDK 25. Compile, test, and runJosm use a JDK 25 toolchain (OpenJDK 25 or GraalVM 25) — runJosm sets javaLauncher so JOSM is not started on the daemon JVM. NumPy’s C API needs JEP 454 (JDK 22+). Compiles against JOSM 19555. The repo must have at least one git commit (generateManifest reads HEAD).

./gradlew test       # headless interop + NumPy + installed-jar classloader tests
./gradlew dist       # plugin jar + graalpy-libs/ → build/dist/
./gradlew releaseZip # zip of graalpy.jar + graalpy-libs/ → build/release/
./gradlew runJosm    # JOSM with the plugin (GraalPy on the classpath)
./install.sh         # copy graalpy.jar and graalpy-libs/ into the real JOSM plugins dir

A GitHub release is a tag v0.1.0 (no v in the plugin version itself). That runs .github/workflows/release.yml, which builds with RELEASE_VERSION from the tag and uploads graalpy.jar plus josm-graalpy-<version>.zip. Do not upload the plugin jar alone: Class-Path needs graalpy-libs/ next to it.

After JOSM opens, create or download an OSM data layer, then:

  • Tools → Run Python hello — adds a tagged node at 49°N, 8.4°E
  • Tools → Run Python centroid — mean lat/lon of the active layer’s nodes via NumPy
  • Tools → Run Python file… — eval a user-chosen .py

Nothing is injected into the script globals. Look up JOSM the Jython way (python.EmulateJython):

from org.openstreetmap.josm.gui import MainApplication
from org.openstreetmap.josm.data.osm import Node, DataSet
from org.openstreetmap.josm.data.coor import LatLon
import numpy as np

dataset = MainApplication.getLayerManager().getActiveDataSet()
n = Node(LatLon(49.0, 8.4))   # there is no Node(lat, lon) in JOSM

java.type("org.openstreetmap.josm…") still works. Other plugins become visible after PluginVisibility injects their PluginClassLoader into ours (constructor + map-frame callback). That is the inverse of lanelet2 ScriptingVisibility, which also injects into this plugin (graalpy). With both loaded, Python 3 can from org.openstreetmap.josm.plugins.lanelet2.api import Lanelet2Extensions — see examples/graalpy/hello_lanelet2.py in the Lanelet2 plugin repository.

Ideas that need SciPy / Shapely / pandas / … live in inspiration/ (Tools → Run Python file…). Only NumPy is installed until you add packages.

For IDE completion while writing scripts (VS Code / IntelliJ), optional JOSM/Java .pyi stubs live in the original Jython collection’s java_pyi_stubs/ directory (stubPath / Pylance). They are editor-only.

NumPy is the GraalPy wheel locked in graalpy.lock (numpy==2.4.4). After changing graalPy.packages, run ./gradlew graalPyLockPackages and commit the lockfile. --enable-native-access=ALL-UNNAMED is required on the test / JOSM JVM.

License

GPL-3.0-or-later (see LICENSE), Copyright (C) 2026 Karlsruhe Institute of Technology (KIT), Institute of Measurement and Control Systems (MRT). This plugin runs inside JOSM (licensed GPL-2.0-or-later), so it is licensed GPLv3 to stay compatible with JOSM's "or later" license.

Maintainer: Richard Schwarzkopf (schwarzkopf@fzi.de).

GraalPy Community Edition JARs in graalpy-libs/ keep their own licenses (primarily UPL 1.0). They are not packed into graalpy.jar.

About

A JOSM Plugin to script with Python3 via GraalPy with full JOSM data and API access, replacing Jython2.7 by the `josm-scripting-plugin`

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages