Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ project(mapget LANGUAGES CXX C)
# Allow version to be set from command line for CI/CD
# For local development, use the default version
if(NOT DEFINED MAPGET_VERSION)
set(MAPGET_VERSION 2025.5.0)
set(MAPGET_VERSION 2025.5.1)
endif()

set(CMAKE_CXX_STANDARD 20)
Expand Down
40 changes: 40 additions & 0 deletions docs/mapget-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,46 @@ If `Accept-Encoding: gzip` is set, the server compresses responses where possibl

JSON Lines is better suited to streaming large responses than a single JSON array. Clients can start processing the first tiles immediately, do not need to buffer the complete response in memory, and can naturally consume the stream with incremental parsers.

### JSONL response format

Each line in the JSONL response is a GeoJSON-like FeatureCollection with additional metadata:

```json
{
"type": "FeatureCollection",
"mapgetTileId": 281479271743500,
"mapId": "EuropeHD",
"mapgetLayerId": "Roads",
"idPrefix": {
"areaId": 123,
"tileId": 456
},
"timestamp": "2025-01-14T10:30:00.000000Z",
"ttl": 3600000,
"error": {
"code": 404,
"message": "Error while contacting remote data source: not found"
},
"features": [...]
}
```

| Field | Type | Description |
|-------|------|-------------|
| `type` | string | Always `"FeatureCollection"` |
| `mapgetTileId` | integer | The mapget tile ID (64-bit decimal) |
| `mapId` | string | Map identifier |
| `mapgetLayerId` | string | Layer identifier within the map |
| `idPrefix` | object | Common ID parts shared by all features in this tile (optional) |
| `timestamp` | string | ISO 8601 timestamp when the tile was created |
| `ttl` | integer | Time-to-live in milliseconds (optional) |
| `error` | object | Error information if tile creation failed (optional) |
| `error.code` | integer | Numeric error code, e.g., HTTP status or database error (optional) |
| `error.message` | string | Human-readable error message (optional) |
| `features` | array | Array of GeoJSON Feature objects |

The `error` object is only present if an error occurred while filling the tile. When present, the `features` array may be empty or contain partial data.

## `/abort` – cancel tile streaming

`POST /abort` cancels a running `/tiles` request that was started with a matching `clientId`. It is useful when the viewport changes and the previous stream should be abandoned.
Expand Down
60 changes: 58 additions & 2 deletions docs/mapget-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ The generator will produce deterministic but varied features for any requested t

<!-- --8<-- [start:geojson] -->

`GeoJsonFolder` serves tiles from a directory containing GeoJSON files. Each file represents one tile and must be named with the tile’s numeric ID in the mapget tiling scheme, for example `123456.geojson`.
`GeoJsonFolder` serves tiles from a directory containing GeoJSON files.

Required fields:

Expand All @@ -214,7 +214,63 @@ sources:
withAttrLayers: true
```

The datasource scans the directory, infers coverage from the file names and converts each GeoJSON feature into mapget’s internal feature model when the corresponding tile is requested.
#### Manifest Mode (Recommended)

If a `manifest.json` file exists in the input directory, it is used to map filenames to tile IDs and layers. This allows arbitrary filenames and multi‑layer support.

**Manifest Structure:**

```json
{
"version": 1,
"metadata": {
"name": "My Dataset",
"description": "Optional description of the dataset",
"source": "OpenStreetMap",
"created": "2024-01-15",
"author": "Your Name",
"license": "CC-BY-4.0"
},
"index": {
"defaultLayer": "GeoJsonAny",
"files": {
"roads.geojson": { "tileId": 121212121212, "layer": "Road" },
"lanes.geojson": { "tileId": 121212121212, "layer": "Lane" },
"other.geojson": { "tileId": 343434343434 },
"simple.geojson": 565656565656
}
}
}
```

**Manifest Fields:**

| Field | Required | Description |
|-------|----------|-------------|
| `version` | No | Manifest format version (default: `1`). |
| `metadata` | No | Optional metadata about the dataset. All sub‑fields (`name`, `description`, `source`, `created`, `author`, `license`) are optional strings. |
| `index` | No | File‑to‑tile mapping configuration. |
| `index.defaultLayer` | No | Default layer name for files without explicit layer (default: `"GeoJsonAny"`). |
| `index.files` | No | Object mapping filenames to tile information. |

**File Entry Formats:**

Each entry in `index.files` maps a filename to tile information. Two formats are supported:

1. **Full format** (object): `{ "tileId": <number>, "layer": "<string>" }`
- `tileId` (required): The mapget tile ID as a 64‑bit unsigned integer.
- `layer` (optional): Layer name. If omitted, uses `defaultLayer`.

2. **Short format** (number): Just the tile ID as a number. Uses `defaultLayer` for the layer name.

This allows multiple GeoJSON files to contribute features to the same tile in different layers, enabling separation of feature types (e.g., roads, lanes, buildings) while sharing the same tile coordinate.

#### Legacy Mode (Deprecated)

!!! warning "Deprecation Notice"
Legacy mode is deprecated and will be removed in a future release. Please migrate to manifest mode by adding a `manifest.json` file to your data directory. When legacy mode is used, a warning is logged to help identify directories that need migration.

If no `manifest.json` exists, the datasource falls back to scanning for files named `<packed-tile-id>.geojson` (e.g., `123456.geojson`). All files are served from a single `GeoJsonAny` layer.

<!-- --8<-- [end:geojson] -->

Expand Down
139 changes: 124 additions & 15 deletions libs/geojsonsource/include/geojsonsource/geojsonsource.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#pragma once

#include <unordered_set>
#include <unordered_map>
#include <string>
#include <optional>

#include "mapget/model/featurelayer.h"
#include "mapget/model/sourcedatalayer.h"
Expand All @@ -11,29 +13,111 @@ namespace mapget::geojsonsource
{

/**
* Data Source which may be used to test a directory which
* contains feature tiles as legacy DBI/LiveLab GeoJSON exports.
* Each tile in the designated directory must be named `<packed-tile-id>.geojson`.
* Entry describing a single GeoJSON file in the manifest.
*/
struct FileEntry
{
std::string filename;
uint64_t tileId = 0;
std::string layer; // Empty means use default layer
};

/**
* Metadata section of the manifest (all fields optional).
*/
struct ManifestMetadata
{
std::optional<std::string> name;
std::optional<std::string> description;
std::optional<std::string> source;
std::optional<std::string> created;
std::optional<std::string> author;
std::optional<std::string> license;
};

/**
* Parsed manifest.json structure.
*/
struct Manifest
{
int version = 1;
ManifestMetadata metadata;
std::string defaultLayer = "GeoJsonAny";
std::vector<FileEntry> files;
};

/**
* Key for looking up files by (tileId, layer).
*/
struct TileLayerKey
{
uint64_t tileId;
std::string layer;

bool operator==(const TileLayerKey& other) const {
return tileId == other.tileId && layer == other.layer;
}
};

struct TileLayerKeyHash
{
std::size_t operator()(const TileLayerKey& k) const {
return std::hash<uint64_t>()(k.tileId) ^ (std::hash<std::string>()(k.layer) << 1);
}
};

/**
* Data Source which may be used to load GeoJSON files from a directory.
*
* Supports two modes of operation:
*
* 1. **Manifest mode** (recommended): If a `manifest.json` file exists in the
* input directory, it is used to map filenames to tile IDs and layers.
* This allows arbitrary filenames and multi-layer support.
*
* Example manifest.json:
* ```json
* {
* "version": 1,
* "metadata": {
* "name": "My Dataset",
* "source": "OpenStreetMap",
* "created": "2024-01-15"
* },
* "index": {
* "defaultLayer": "GeoJsonAny",
* "files": {
* "roads.geojson": { "tileId": 121212121212, "layer": "Road" },
* "lanes.geojson": { "tileId": 121212121212, "layer": "Lane" },
* "other.geojson": { "tileId": 343434343434 }
* }
* }
* }
* ```
*
* You may use the script docs/export-classic-routing-tiles.py, to
* generate an export of NDS.Classic tiles which can be used
* with this class.
* 2. **Legacy mode**: If no manifest.json exists, falls back to scanning for
* files named `<packed-tile-id>.geojson`. All files go into a single
* "GeoJsonAny" layer.
*
* Note: This data source was mainly developed as a scalability test
* scenario for erdblick, and should NOT BE USED IN PRODUCTION.
* In the future, the DBI will export the same GeoJSON feature model that
* is understood by mapget, and a GeoJSON data source will be part
* of the mapget code base.
* scenario for erdblick. In the future, the DBI will export the same
* GeoJSON feature model that is understood by mapget, and a GeoJSON
* data source will be part of the mapget code base.
*/
class GeoJsonSource : public mapget::DataSource
{
public:
/**
* Construct a GeoJSON data source from a directory containing
* GeoJSON tiles like <packed-tile-id>.geojson.
* @param inputDir The directory with the GeoJSON tiles.
* @param withAttrLayers Flag indicating, whether compound GeoJSON
* Construct a GeoJSON data source from a directory.
*
* If a manifest.json exists in the directory, it will be used for
* file-to-tile mapping and layer configuration. Otherwise, falls back
* to legacy mode where files must be named `<tile-id>.geojson`.
*
* @param inputDir The directory with the GeoJSON files (and optional manifest.json).
* @param withAttrLayers Flag indicating whether compound GeoJSON
* properties shall be converted to mapget attribute layers.
* @param mapId Optional map ID override. If empty, derived from inputDir.
*/
GeoJsonSource(const std::string& inputDir, bool withAttrLayers, const std::string& mapId="");

Expand All @@ -42,11 +126,36 @@ class GeoJsonSource : public mapget::DataSource
void fill(mapget::TileFeatureLayer::Ptr const&) override;
void fill(mapget::TileSourceDataLayer::Ptr const&) override;

/** Returns true if a manifest.json was found and used. */
[[nodiscard]] bool hasManifest() const { return hasManifest_; }

/** Returns the parsed manifest (only valid if hasManifest() is true). */
[[nodiscard]] const Manifest& manifest() const { return manifest_; }

private:
/** Parse manifest.json from the input directory. Returns true if found and valid. */
bool parseManifest();

/** Initialize coverage from manifest entries. */
void initFromManifest();

/** Initialize coverage by scanning directory for <tile-id>.geojson files (legacy). */
void initFromDirectory();

/** Create LayerInfo JSON for a given layer name. */
static nlohmann::json createLayerInfoJson(const std::string& layerName);

mapget::DataSourceInfo info_;
std::unordered_set<uint64_t> coveredMapgetTileIds_;
std::string inputDir_;
bool withAttrLayers_ = true;
bool hasManifest_ = false;
Manifest manifest_;

// Mapping from (tileId, layer) -> filename
std::unordered_map<TileLayerKey, std::string, TileLayerKeyHash> tileLayerToFile_;

// Set of covered tile IDs per layer (for legacy single-layer mode compatibility)
std::unordered_map<std::string, std::unordered_set<uint64_t>> layerCoverage_;
};

} // namespace mapget::geojsonsource
Loading
Loading