Imported from Zkir/UrbanEye3D (
AGENTS.md). Install upstream withnpx skills add Zkir/UrbanEye3D. Copyright stays with the author.
Urban Eye 3D – JOSM 3D Viewer Plugin
Operation instructions
- JAVA version: use JAVA 11
- Definition of Done: A task is considered DONE only when:
mvn packagecompletes successfully without any errors.- Successful execution of manual test confirmed by the human.
- Unit test is created or at least proposed.
- DEVBLOG.md file is updated, including (but not limiting to) the following sections: Recent Accomplishments, Architecture and Key Concepts, and if necessary, Next Steps. Recent Accomplishments should incude date, and be focused on value for end-user/product, but without marketing bullshit, not on technical details.
- features.md is reviewed and updated if necessary.
- Do not suggest git commits. Git commits in this project are allowed for protein-based developers only.
- JOSM source code can be found in d:\UrbanEye3D\ext_sources\josm_source
- Use UrbanEye3dPlugin.debugMsg() for debug messages instead of System.out.println().
- The
GroundPlaneTestautotest is not stable and should be run several times in case of failure.
Goals
- Create a JOSM plugin that displays loaded buildings (including
building:part=*) and other objects in a separate 3D window, making creation and editing of 3d building in OSM easier. - Make it possible to generate more realistic 3D buildings based on OSM data, including windows, cornices, doors, entrances and building passages.
Next Steps
Musts for the Next Release
- Currently, none??
JOSM patches to monitor
- [MUST BE FIXED] https://josm.openstreetmap.de/ticket/24699
Nice to have in the Next Release
- None currently
Feature candidates
-
New generation procedures We need generation procedures the following objects. Pre-made models do not fit, because the
heighttag should be respected.- Wind generator:
power=generator+generator:source=wind/generator:method=wind_turbine- F4 supports it. - Communication towers/masts:
man_made=mast
- Wind generator:
-
Support windows/facades
- Buildings with windows are nice. This feature is present in osm2world, so we also want it.
- There is a tag in osm for windows: window=*.
- We want to implement "facade" feature similar to X-plane one. https://developer.x-plane.com/article/facade-creation
- We already have some sample facades: https://github.com/Zkir/VFR_LANDMARKS_3D_RU/blob/master/Facades
-
Increase resolution for GroundTile/MapCSS style.
- Some kind of smart scaling is required, for the nearest tiles only, because it will create huge performance impact otherwise.
Ideas for the Further Development
See: IDEAS.md
Recent Accomplishments
See Devblog
- See Devblog for the full development history and recent changes.
Architecture and Key Concepts
This section combines high-level architectural overview with key lessons learned during development.
Code Structure
src
├── main
│ └── java
│ └── ru
│ └── zkir
│ ├── customtms // Module for working with satellite imagery (TMS).
│ │ └── ... // Contains the implementation for tile loading and caching,
│ │ // as well as the definition of imagery providers.
│ │
│ ├── easytext // Internationalization (i18n) module, implemented purely in Java.
│ │ └── ... // Replaces external utilities for parsing PO/POT files
│ │ // and compiling binary LANG files for JOSM.
│ │
│ └── urbaneye3d // Main module for the "UrbanEye3D" JOSM plugin.
│ ├── UrbanEye3dPlugin.java // Main plugin class, entry point.
│ ├── DialogWindow3D.java // Dockable window for displaying the 3D scene.
│ └── ... // All other files :)
│
└── test
└── java
└── ru
└── zkir
├── customtms // Tests for the TMS engine.
│ └── ... //More or less independent from main plugin funtionality
│
└── urbaneye3d
├── RoofGeneratorTopologyTest.java // Tests the topology of generated 3D roof models.
└── ... // Other important tests :)
JOSM Framework Integration
- Entry Point & UI: The plugin is initiated by
UrbanEye3dPlugin.java, which launches the main dockable window,DialogWindow3D.java. This dialog manages theRenderer3Dcanvas, which handles all OpenGL rendering. - Text Rendering in OpenGL: To display 2D text over a 3D scene in JOGL,
com.jogamp.opengl.util.awt.TextRendereris a powerful tool. It requires initialization with a Font and follows a lifecycle:beginRendering(width, height), followed bydraw(text, x, y)calls, and finallyendRendering(). - Internationalization with Placeholders: For localizing strings that contain dynamic data (like counts or times), the JOSM
tr()function supports positional placeholders (e.g.,{0},{1}). This is much more robust than manual string concatenation, as it allows translators to reorder the data as needed for their language's grammar. - Extensibility: The plugin extends the JOSM environment in several ways:
- Validation: Custom tests are added to the JOSM validator by extending
org.openstreetmap.josm.data.validation.Test. - Actions & Shortcuts: New keyboard shortcuts are created by extending
JosmAction. - Preferences: A settings panel is added to the
ToggleDialogby providing a preference class to its constructor.
- Validation: Custom tests are added to the JOSM validator by extending
- Documenting Quirks: Experience has shown it is vital to document non-obvious framework behaviors. For example, JOSM's MapCSS engine resolves image paths relative to the global
resources/images/directory, not the CSS file's location. Documenting these discoveries saves significant time for future development.
Internationalization
- JOSM uses a non-trivial internationalization (i18n) system that compiles text-based
.pofiles into binary.langfiles using a specific Perl script..pofiles are created via xgettext utility, which is a living classics of the industry, but is still an external dependency. - We have rewritten everything into pure Java (see
ru.zkir.easytextpackage), both collecting string for pot creation and complingpointotolang. Both functions are integrated into Maven build (pom.xml) using theexec-maven-plugin. - There is an autotest that enforces that all po files are converted to lang files and print report of translation completeness.
- There is still
I18n.bat, which include calls to traditional josm toolchain. It should not be used in normal process, only in case of bugs inru.zkir.easytextjava solution. Note that you are on your own regarding the installation of gettext and JOSM I18n.
3D Geometry Generation
- Core Principle: Watertight Meshes: All 3D models, especially roofs, must be generated as watertight (fully enclosed) meshes with consistent, outward-facing normals. This is fundamental for correct rendering and future features like ambient occlusion. This is enforced by unit tests (
RoofGeneratorTopologyTest). Those autotests have helped greatly during development of geometry generation code. - Non-triangulated Meshes: The plugin deliberately maintains meshes in their original, non-triangulated form (using polygons and quads where possible). This is essential for the wireframe mode, as triangulated meshes would appear cluttered and confusing with unnecessary diagonal lines. Keeping the original polygons also makes it immediately obvious what kind of geometry is being generated by the plugin's algorithms.
- Coordinate System: The plugin deliberately avoids using JOSM's projected
EastNorthcoordinates. Instead, it uses geographicLatLoncoordinates and performs its own projection to a local 3D Cartesian system. This is crucial becauseEastNorthcoordinates are distorted by map projection and are not directly comparable to height values, which would lead to malformed 3D shapes. - Roof Generation Factory: The
roofgeneratorspackage uses a factory pattern. TheRoofShapesenum maps OSMroof:shapetags to specificMesherimplementations (e.g.,MesherHipped), making the system easily extensible for new roof types.
Rendering Pipeline
- Technology: The scene is rendered using JOGL (OpenGL for Java). The current implementation uses an immediate-mode-style pipeline, with plans to modernize it with shaders.
- Ground Plane Imagery:
- We have two modes for ground plane: satellite imagery (loaded from TMS) and live data based imagery (rendered from the loaded osm data on-the-fly using MapCSS). Both modes faced significant challenges.
- Josm has a lot of different satellite layers, but it tightly coupled with the main map window. I failed to reuse existing josm code in the plugin and had to create own(!) simple TMS rendering library, ru.zkir.customtms. It works fine, but probably should be eventually replaced with 'standard' josm calls, because some layers, e.g. MapBox, cannot be used without API key.
- JOSM MapCSS engine is also quite strange. I've managed to decouple it from JOSM main window, so we have now own MapCSS styles for 3D window. However, JOSM MapCSS engine has some single-threaded bottlenechs and some bugs (rendering cannot be properly interrupted). So it's a big area for [performance] improvement.
Data-Driven Inference
In 3D rendering, many objects require specific attributes that are often missing in OSM for particular objects. For instance, the engine cannot render a "generic" tree; it must choose between broadleaved, needleleaved, or palm models. Usually, this choice could be made according to the leaf_type tag, but if it is missing, it can be inferred from other tags (like species) or even from geographic location (e.g., palms are appropriate in Greece but impossible in Siberia).
To keep the JOSM plugin lightweight and maintainable, UrbanEye3D avoids the two extremes: either fragile hardcoded heuristics or heavy machine learning libraries. Instead, it utilizes a Maximum Likelihood inference powered by pre-calculated global OSM statistics:
- Spatial Grid for Vegetation: When explicit tags are missing, the engine uses a weighted spatial grid (
spatial_stats_5x5.json) to predict the most likelyleaf_typebased on coordinates. - Statistical Rules for Flags: The
FlagColorInferencesystem usesflag_rules.jsonto predict flag colors based on metadata likesubject,country, orwikidata. The heavy statistical lifting is done offline in the Python data pipeline (See some documentation in themiscfolder), while the Java plugin remains simple, fast, and data-driven.
Botanical Engine
As mentioned in the inference section, the plugin needs to know the leaf_type of a tree to select the correct 3D model or texture. Since this data is often missing in OSM, the plugin employs two specialized botanical databases:
- Tree Species Database: Contains approximately 2000 tree species names with their corresponding
leaf_typeandleaf_cycle. It is used to automatically determine the leaf type whenspeciesorgenustags are present. The full list is available in tree_species.csv, and a human-readable report can be found in docs/tree_species.md. - Spatial Database (spatial_stats_5x5.json): Contains the most likely leaf types (and, for the future use, most likely species) for each 5x5 degree grid cell. It is used when no additional tags are available, relying on the well-known principle of forest zoning (e.g., defaulting to needleleaved trees in the taiga or palms in the tropics).
Data Pipeline: Both databases are generated based on global OSM statistics using the pipeline in the misc folder. This pipeline should be executed before every release to ensure the data is up-to-date. In addition to OSM data, it utilizes the POWO (Plants of the World Online) Web API to ensure the species list contains Accepted scientific names and valid synonyms rather than arbitrary entries.
Species Normalization: Before performing a database lookup, species names undergo mandatory normalization. This allows the grouping of statistics for synonyms (e.g., Quercus pedunculata and Quercus robur are recognized as the same species) and ensures that cultivars or variations that do not affect the leaf type are ignored.
Normalization Examples:
Tilia cordata green spire→Tilia cordataPlatanus x acerifolia→Platanus × acerifoliaTilia × europaea 'Pallida'→Tilia × europaea
Testing Strategy / Test driven development
- A unit testing suite has been set up using JUnit 5. To run the tests, execute
mvn testfrom the project root. - A Test-Driven Development (TDD) approach proved highly effective in this project. Since it's a JOSM plugin, you cannot debug it directly. However, autotests can be run and debugged separately, without JOSM. So it make sence to develop some feature test it in isolation.
- Automated checks for mesh validity do not let AI/LLM produce crap and report success.
- There are several autotests for different things, both "functional" (to test functionality) and "pseudo tests" to collect statistics.
| Test name | Details |
|---|---|
| AssetListTest.java | Test for verifying and documenting project assets (textures, models). Scans the src/main/resources directory, compares found assets with a master list, and generates ASSET-LIST.md. |
| GroundPlaneTest.java | Verifies the correct creation, loading, and rendering of Ground Plane tiles for satellite imagery or MapCSS data. Includes tests for cache clearing and behavior during rapid panning. |
| I18nStatusTest.java | Test for checking internationalization status. Reads .po and .pot files, calculates translation coverage, and verifies the existence of compiled .lang files. Generates translation-status report. |
| MapCSSTest.java | Verifies the syntax of project MapCSS files, the existence of referenced resources (e.g., images), and the rendering of OSM data using MapCSS. |
| RoofGeneratorGoldenMasterTest.java | Compares the output of 3D geometry generators (in OBJ format)against a verified "golden" result to ensure regression stability. Tests various roof shapes on different bases. |
| RoofGeneratorTopologyTest.java | Tests the topology of generated 3D roof models. Verifies watertightness, correct normals, absence of zero-length edges, self-intersections, and duplicate vertices. Includes tests for all roof shapes and special cases (with holes, different orientations). |
| SceneTest.java | Integration tests for the Scene component. Verifies the correct construction of the 3D scene from various OSM data (buildings with parts, multipolygons, barriers, trees). Analyzes how Scene interprets data and forms RenderableElement objects. |
| TagInfoGeneratorTest.java | Does not really test anything, but collects used tags from the source code and produces taginfo.json, so we can take a look at used tags. |
| TreeSpeciesReportTest.java | Generates a Markdown report (docs/tree_species.md) listing all supported tree species from the internal database. |
| ValidatorTest.java | Tests for custom JOSM validators (SpatialConsistencyChecks, TagChecks). Verifies that validators correctly identify expected errors and do not produce false positives on valid data. |
OSM Data Processing Pipeline (misc subproject)
The misc folder contains a specialized subproject for large-scale processing of OpenStreetMap data. This pipeline starts with the global planet-latest.osm.pbf file and serves to extract, analyze, and enrich botanical and building data for the main plugin.
- There are the following main goals for this pipeline:
- Create the tree species list (
src/main/resources/data/tree_species.csv) which is used to inferleaf_typetag, in case it is missing, in order to select most appropriate tree texture/model. - Generate geographic tree statistics (
src/main/resources/data/spatial_stats_5x5.json) based on OSM data to provide realistic rendering defaults when explicit tags are missing. - Correct typo errors in
speciestag in the OSM database itself, creating osm-files with changes, so that they can be uploaded via JOSM. - Create smart defaults for various building attributes, depending on building type (
building=*value). This is not yet used currently by the plugin.
- Create the tree species list (
- See misc/AGENTS.md for details.
The Urban Eye is watching you!
