diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2c141fa..dcf2969 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,9 +35,12 @@ repos: hooks: - id: ruff args: [--fix] + exclude: '__init__.py' + - repo: https://github.com/pre-commit/mirrors-mypy rev: 'v1.5.1' hooks: - id: mypy additional_dependencies: [types-requests==2.31.0.1] + args: [--follow-imports=skip] diff --git a/docs/tiling.md b/docs/tiling.md new file mode 100644 index 0000000..470b2ae --- /dev/null +++ b/docs/tiling.md @@ -0,0 +1,277 @@ +# Integration of TileJSON with MicroJSON +## Purpose +This specification outlines how to use TileJSON to integrate tiled MicroJSON data. It provides examples of how TileJSON can be used to specify the tiling scheme and zoom levels for MicroJSON data. It is based on the [TileJSON 3.0.0 specification](https://github.com/mapbox/tilejson-spec/blob/master/3.0.0/README.md), but extends it by recommending additional properties include the integration of MicroJSON data and fit purposes of microscopy data visualization. The recommendations provided here are not intrinsic to the original TileJSON specification but have been tailored to suit the needs of microscopy data visualization and integration with MicroJSON. However, all suggestions are designed to maintain compatibility with the original TileJSON specification. + +## Background of TileJSON + +[TileJSON](https://github.com/mapbox/tilejson-spec/) is a widely-used format in mapping applications for specifying tilesets. Developed to streamline the integration of different map layers, TileJSON is essential for ensuring consistency across mapping platforms. It describes tilesets through a JSON object, detailing properties like tile URLs, zoom levels, and spatial coverage. + + +## TileJSON for MicroJSON Object Structure + +- **`tilejson`:** Specifies the version of the TileJSON spec being used. Required for all TileJSON objects. +- **`name`:** The name of the tileset. Optional but recommended. +- **`description`:** Provide a brief description of the tileset. Optional but recommended. +- **`version`:** The version of the tileset. Optional but recommended. +- **`attribution`:** A link to the data source or other attribution information, e.g. organisational origin. Optional but recommended. +- **`tiles`:** Required. The URL pattern for accessing the vector tiles. The `urlbase/{zlvl}/{t}/{c}/{z}/{x}/{y}` is the recommended default naming pattern for the tiles, in this order, where `urlbase` is the base URL (e.g. `http://example.com/tiles`), `{zlvl}` is the zoom level, `{t}` is the tileset timestamp, `{c}` is the channel, `{z}` is the z coordinate, and `{x}` and `{y}` are the x and y coordinates, respectively. +- **`minzoom` and `maxzoom`:** Defines the range of zoom levels for which the tiles are available. +- **`bounds`:** Optional. Specifies the geometrical bounds included in the tileset. Specified as an array of minimum four numbers in the order `[minX, minY, maxX, maxY]`, but may include up to a further six numbers for a total of ten, `[minT, minC, minZ, minX, minY, maxT, maxC, maxZ, maxX, maxY]`, where `minT` is the minimum tileset timestamp, `minC` is the minimum channel, `minZ` is the minimum z coordinate, `minX` and `minY` are the minimum x and y coordinates, `maxT` is the maximum tileset timestamp, `maxC` is the maximum channel, `maxZ` is the maximum z coordinate, and `maxX` and `maxY` are the maximum x and y coordinates. +- **`center`:** Optional. Indicates the center and suggested default view of the tileset. Minimum of three numbers in the order `[x, y, zoom]`, but may include up to a further three numbers for a total of six, `[t,c,z,x,y,zoom]`, where `t` is the tileset timestamp, `c` is the channel, `z` is the z coordinate, `x` and `y` are the x and y coordinates, and `zoom` is the zoom level. Zoom level should be last. +- **`vector_layers`:** Required. Describes each layer within the vector tiles, and has the following structure: + + - **`id`:** Required. A unique identifier for the layer. Required for each layer. + - **`fields`:** Required. A list of fields (attributes) and their data types. For MicroJSON, this can either be an empty list, or any combination of `string`, `numerical`, and `multi-numeric`, attributes with their sub-attributes, as described in the MicroJSON specification. Required for each layer. + - **`description`:** Optional. A brief description of the layer. + - **`minzoom` and `maxzoom`:** Optional. The range of zoom levels at which the layer is visible. +- **`fillzoom`:** Optional. An integer specifying the zoom level from which to generate overzoomed tiles. +- **`legend`:** Optional. Contains a legend to be displayed with the tileset. + +The following fields of TileJSON may be used if the use case requires it, and are included here for completeness: + +- **`scheme`:** The tiling scheme of the tileset. +- **`grids`:** The URL pattern for accessing grid data. +- **`data`:** Optional. The URL pattern for accessing data. Used for GeoJSON originally, which in this specification is replaced by MicroJSON and used in the `tiles` field. +- **`template`:** Optional. Contains a mustache template to be used to format data from grids for interaction. + +## Pydantic Model for TileJSON for MicroJSON +### TileJSON +::: microjson.tile.TileJSON +### TileModel +::: microjson.tile.TileModel +### TileLayer +::: microjson.tile.TileLayer + +## TileJSON for MicroJSON example with Vector Layers +The below example illustrates a TileJSON for a MicroJSON tileset multiple layers of detail. The tileset has a single vector layer, `image_layer` id of `vector_layers`, which contains a single vector layer describing images. The `fields` property of the this layer specifies the attributes of the layer, including the data types of the attributes. The `tiles` property specifies the URL pattern for accessing the vector tiles, which in this case is a 2D data set (no channels, time or z-axis) with zoom level. The `fillzoom` property specifies the zoom level from which to generate overzoomed tiles, which in this case starts at level 3, after the last specified layer. + +This file is located in the `examples/tiles` directory of the repository, and is named `tiled_example.json`. It has a corresponding MicroJSON file for each tile, located in the `examples/tiles/tiled_example` directory of the repository. The MicroJSON files are organized according to the tiling scheme, with the directory structure `zlvl/x/y.json` where `zlvl` is the zoom level, `x` is the x coordinate, and `y` is the y coordinate. The MicroJSON files contain the MicroJSON objects for the corresponding tiles, and are named according to the tiling scheme. For example, the MicroJSON object for the tile at zoom level 1, tile at (0,1) in the tiling scheme is located at `examples/tiles/tiled_example/1/0/1.json`. Examples for MicroJSON objects at zoom levels 0, 1, and 2 are provided below. + +```json +{ + { + "tilejson": "3.0.0", + "name": "2D Data Example", + "description": "A tileset showing 2D data with multiple layers of detail.", + "version": "1.0.0", + "attribution": "Example", + "tiles": [ + "http://example.com/tiled_example/{zlvl}/{x}/{y}.json" + ], + "minzoom": 0, + "maxzoom": 10, + "bounds": [0, 0, 24000, 24000], + "center": [12000, 12000, 0], + "vector_layers": [ + { + "id": "image_layer", + "description": "Image layer", + "minzoom": 0, + "maxzoom": 10, + "fields": { + "string": { + "Plate": "Plate ID", + "Image": "Image URI" + }, + "numerical": { + "Label": "Label ID", + "Channel": "Channel ID" + } + } + } + ], + "fillzoom": 3 +} +``` + +## Tiled MicroJSON Example +### Level 0 +The following is an example of a MicroJSON object at zoom level 0, tile at (0,0) in the tiling scheme. Example URL: `http://example.com/tiles/0/0/0.json` +```json +{ + "tilejson": "3.0.0", + "name": "2D Data Example", + "description": "A tileset showing 2D data with multiple layers of detail.", + "version": "1.0.0", + "attribution": "Example", + "tiles": [ + "http://example.com/tiled_example/{zlvl}/{x}/{y}.json" + ], + "minzoom": 0, + "maxzoom": 10, + "bounds": [0, 0, 24000, 24000], + "center": [12000, 12000, 0], + "format": "json", + "vector_layers": [ + { + "id": "image_layer", + "description": "Image layer", + "minzoom": 0, + "maxzoom": 10, + "fields": { + "string": { + "Plate": "Plate ID", + "Image": "Image URI" + }, + "numerical": { + "Label": "Label ID", + "Channel": "Channel ID" + } + } + } + ], + "fillzoom": 3 +} +``` + +### Level 1 +The following is an example of a MicroJSON object at zoom level 1, tile at (0,1) in the tiling scheme. Example URL: `http://example.com/tiles/1/0/1.json` +```json +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 0, + 10000 + ], + [ + 10000, + 10000 + ], + [ + 10000, + 20000 + ], + [ + 0, + 20000 + ], + [ + 0, + 10000 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 3 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 3, + "max": 3 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} +``` + +### Level 2 +The following is an example of a MicroJSON object at zoom level 2, tile at (1,1) in the tiling scheme. Example URL: `http://example.com/tiles/2/1/3.json` +```json +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 5000, + 15000 + ], + [ + 10000, + 15000 + ], + [ + 10000, + 20000 + ], + [ + 5000, + 20000 + ], + [ + 5000, + 15000 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 13 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 13, + "max": 13 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} +``` diff --git a/examples/tiles/tiled_example.json b/examples/tiles/tiled_example.json new file mode 100644 index 0000000..cc634b3 --- /dev/null +++ b/examples/tiles/tiled_example.json @@ -0,0 +1,33 @@ +{ + "tilejson": "3.0.0", + "name": "2D Data Example", + "description": "A tileset showing 2D data with multiple layers of detail.", + "version": "1.0.0", + "attribution": "Example", + "tiles": [ + "http://example.com/tiled_example/{zlvl}/{x}/{y}.json" + ], + "minzoom": 0, + "maxzoom": 10, + "bounds": [0, 0, 24000, 24000], + "center": [12000, 12000, 0], + "vector_layers": [ + { + "id": "image_layer", + "description": "Image layer", + "minzoom": 0, + "maxzoom": 10, + "fields": { + "string": { + "Plate": "Plate ID", + "Image": "Image URI" + }, + "numerical": { + "Label": "Label ID", + "Channel": "Channel ID" + } + } + } + ], + "fillzoom": 3 +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/0/0/0.json b/examples/tiles/tiled_example/0/0/0.json new file mode 100644 index 0000000..eb14121 --- /dev/null +++ b/examples/tiles/tiled_example/0/0/0.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 0, + 0 + ], + [ + 20000, + 0 + ], + [ + 20000, + 20000 + ], + [ + 0, + 20000 + ], + [ + 0, + 0 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 1 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 1, + "max": 1 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/1/0/0.json b/examples/tiles/tiled_example/1/0/0.json new file mode 100644 index 0000000..cefbcf8 --- /dev/null +++ b/examples/tiles/tiled_example/1/0/0.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 0, + 0 + ], + [ + 10000, + 0 + ], + [ + 10000, + 10000 + ], + [ + 0, + 10000 + ], + [ + 0, + 0 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 2 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 2, + "max": 2 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/1/0/1.json b/examples/tiles/tiled_example/1/0/1.json new file mode 100644 index 0000000..a1abd4e --- /dev/null +++ b/examples/tiles/tiled_example/1/0/1.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 0, + 10000 + ], + [ + 10000, + 10000 + ], + [ + 10000, + 20000 + ], + [ + 0, + 20000 + ], + [ + 0, + 10000 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 3 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 3, + "max": 3 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/1/1/0.json b/examples/tiles/tiled_example/1/1/0.json new file mode 100644 index 0000000..6786f83 --- /dev/null +++ b/examples/tiles/tiled_example/1/1/0.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 10000, + 0 + ], + [ + 20000, + 0 + ], + [ + 20000, + 11000 + ], + [ + 10000, + 10000 + ], + [ + 10000, + 0 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 4 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 4, + "max": 4 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/1/1/1.json b/examples/tiles/tiled_example/1/1/1.json new file mode 100644 index 0000000..aae24dc --- /dev/null +++ b/examples/tiles/tiled_example/1/1/1.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 10000, + 10000 + ], + [ + 20000, + 10000 + ], + [ + 20000, + 20000 + ], + [ + 10000, + 20000 + ], + [ + 10000, + 10000 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 5 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 5, + "max": 5 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/2/0/0.json b/examples/tiles/tiled_example/2/0/0.json new file mode 100644 index 0000000..cac0422 --- /dev/null +++ b/examples/tiles/tiled_example/2/0/0.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 0.0, + 0.0 + ], + [ + 5000, + 0 + ], + [ + 5000, + 5000 + ], + [ + 0, + 5000 + ], + [ + 0, + 0 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 6 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 6, + "max": 6 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/2/0/1.json b/examples/tiles/tiled_example/2/0/1.json new file mode 100644 index 0000000..95d2320 --- /dev/null +++ b/examples/tiles/tiled_example/2/0/1.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 0, + 5000 + ], + [ + 5000, + 5000 + ], + [ + 5000, + 10000 + ], + [ + 0, + 10000 + ], + [ + 0, + 5000 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 7 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 7, + "max": 7 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/2/0/2.json b/examples/tiles/tiled_example/2/0/2.json new file mode 100644 index 0000000..69c468c --- /dev/null +++ b/examples/tiles/tiled_example/2/0/2.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 0.0, + 10000 + ], + [ + 5000, + 10000 + ], + [ + 5000, + 15000 + ], + [ + 0, + 15000 + ], + [ + 0, + 10000 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 8 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 8, + "max": 8 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/2/0/3.json b/examples/tiles/tiled_example/2/0/3.json new file mode 100644 index 0000000..d8b321d --- /dev/null +++ b/examples/tiles/tiled_example/2/0/3.json @@ -0,0 +1,72 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 0.0, + 15000.0 + ], + [ + 5000, + 15000 + + ], + [ + 5000, + 20000 + ], + [ + 0, + 20000 + ], + [ + 0, + 15000 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 9 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 9, + "max": 9 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/2/1/0.json b/examples/tiles/tiled_example/2/1/0.json new file mode 100644 index 0000000..f5d3f30 --- /dev/null +++ b/examples/tiles/tiled_example/2/1/0.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 5000, + 0 + ], + [ + 10000, + 0 + ], + [ + 10000, + 5000 + ], + [ + 5000, + 5000 + ], + [ + 5000, + 0 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 10 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 10, + "max": 10 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/2/1/1.json b/examples/tiles/tiled_example/2/1/1.json new file mode 100644 index 0000000..9bde081 --- /dev/null +++ b/examples/tiles/tiled_example/2/1/1.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 5000, + 5000 + ], + [ + 10000, + 5000 + ], + [ + 10000, + 10000 + ], + [ + 5000, + 10000 + ], + [ + 5000, + 5000 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 11 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 11, + "max": 11 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/2/1/2.json b/examples/tiles/tiled_example/2/1/2.json new file mode 100644 index 0000000..a16b7ca --- /dev/null +++ b/examples/tiles/tiled_example/2/1/2.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 5000, + 10000 + ], + [ + 10000, + 10000 + ], + [ + 10000, + 15000 + ], + [ + 5000, + 15000 + ], + [ + 5000, + 10000 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 12 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 12, + "max": 12 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/2/1/3.json b/examples/tiles/tiled_example/2/1/3.json new file mode 100644 index 0000000..4b121ff --- /dev/null +++ b/examples/tiles/tiled_example/2/1/3.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 5000, + 15000 + ], + [ + 10000, + 15000 + ], + [ + 10000, + 20000 + ], + [ + 5000, + 20000 + ], + [ + 5000, + 15000 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 13 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 13, + "max": 13 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/2/2/0.json b/examples/tiles/tiled_example/2/2/0.json new file mode 100644 index 0000000..610f4e4 --- /dev/null +++ b/examples/tiles/tiled_example/2/2/0.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 10000, + 0 + ], + [ + 15000, + 0 + ], + [ + 15000, + 5000 + ], + [ + 10000, + 5000 + ], + [ + 10000, + 0 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 14 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 14, + "max": 14 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/2/2/1.json b/examples/tiles/tiled_example/2/2/1.json new file mode 100644 index 0000000..639910a --- /dev/null +++ b/examples/tiles/tiled_example/2/2/1.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 10000, + 5000 + ], + [ + 15000, + 5000 + ], + [ + 15000, + 10000 + ], + [ + 10000, + 10000 + ], + [ + 10000, + 5000 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 15 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 15, + "max": 15 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/2/2/2.json b/examples/tiles/tiled_example/2/2/2.json new file mode 100644 index 0000000..738c6da --- /dev/null +++ b/examples/tiles/tiled_example/2/2/2.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 10000, + 10000 + ], + [ + 15000, + 10000 + ], + [ + 15000, + 15000 + ], + [ + 10000, + 15000 + ], + [ + 10000, + 10000 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 16 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 16, + "max": 16 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/2/2/3.json b/examples/tiles/tiled_example/2/2/3.json new file mode 100644 index 0000000..3fdd6f9 --- /dev/null +++ b/examples/tiles/tiled_example/2/2/3.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 10000, + 15000 + ], + [ + 15000, + 15000 + ], + [ + 15000, + 20000 + ], + [ + 10000, + 20000 + ], + [ + 10000, + 15000 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 17 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 17, + "max": 17 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/2/3/0.json b/examples/tiles/tiled_example/2/3/0.json new file mode 100644 index 0000000..0b42ef5 --- /dev/null +++ b/examples/tiles/tiled_example/2/3/0.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 15000, + 0 + ], + [ + 20000, + 0 + ], + [ + 20000, + 5000 + ], + [ + 15000, + 5000 + ], + [ + 15000, + 0 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 18 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 18, + "max": 18 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/2/3/1.json b/examples/tiles/tiled_example/2/3/1.json new file mode 100644 index 0000000..e22c842 --- /dev/null +++ b/examples/tiles/tiled_example/2/3/1.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 15000, + 5000 + ], + [ + 20000, + 5000 + ], + [ + 20000, + 10000 + ], + [ + 15000, + 10000 + ], + [ + 15000, + 5000 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 19 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 19, + "max": 19 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/2/3/2.json b/examples/tiles/tiled_example/2/3/2.json new file mode 100644 index 0000000..fc4b5a6 --- /dev/null +++ b/examples/tiles/tiled_example/2/3/2.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 15000, + 10000 + ], + [ + 20000, + 10000 + ], + [ + 20000, + 15000 + ], + [ + 15000, + 15000 + ], + [ + 15000, + 10000 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 20 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 20, + "max": 20 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/examples/tiles/tiled_example/2/3/3.json b/examples/tiles/tiled_example/2/3/3.json new file mode 100644 index 0000000..b3fdd05 --- /dev/null +++ b/examples/tiles/tiled_example/2/3/3.json @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 15000, + 15000 + ], + [ + 20000, + 15000 + ], + [ + 20000, + 20000 + ], + [ + 15000, + 20000 + ], + [ + 15000, + 15000 + ] + ] + ] + }, + "properties": { + "numeric": { + "Label": 21 + } + } + } + ], + "coordinatesystem": { + "axes": [ + { + "name": "x", + "type": "cartesian", + "unit": "micrometer", + "description": "x-axis" + }, + { + "name": "y", + "type": "cartesian", + "unit": "micrometer", + "description": "y-axis" + } + ] + }, + "value_range": { + "Label": { + "min": 21, + "max": 21 + } + }, + "properties": { + "string": { + "Plate": "label", + "Image": "x00_y01_p01_c1.ome.tif" + }, + "numeric": { + "Channel": 1.0 + } + } +} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 4d99b9b..0ab2b8f 100755 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,6 +44,7 @@ nav: - JSON validation: 'validation.md' - Metadata example: 'metadata_example.md' - Provenance in MicroJSON: 'provenance.md' + - MicroJSON tiling: 'tiling.md' - GitHub: https://github.com/polusai/microjson - Pypi: https://pypi.org/project/microjson/ plugins: diff --git a/mypy.ini b/mypy.ini index 42ed72b..13de753 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,2 +1,3 @@ [mypy] disallow_any_generics = False +exclude = src/microjson/utils.py diff --git a/pyproject.toml b/pyproject.toml index 687d19b..f66a9c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "microjson" -version = "0.1.9" +version = "0.1.10" description = "MicroJSON is a library for validating, parsing, and manipulating MicroJSON data." readme = "README_short.md" authors = ["Bengt Ljungquist "] @@ -55,7 +55,7 @@ mkdocstrings = "^0.23.0" testpaths = ["tests/"] [tool.bumpver] -current_version = "0.1.9" +current_version = "0.1.10" version_pattern = "MAJOR.MINOR.PATCH" commit_message = "Bump version {old_version} -> {new_version}" commit = true diff --git a/src/microjson/__init__.py b/src/microjson/__init__.py index 1ed73e1..6c16999 100644 --- a/src/microjson/__init__.py +++ b/src/microjson/__init__.py @@ -1,5 +1,6 @@ from .model import MicroJSON, GeoJSON -from .utils import gather_example_files,OmeMicrojsonModel, MicrojsonBinaryModel - -__version__ = "0.1.9" +from .utils import gather_example_files +from .utils import OmeMicrojsonModel +from .utils import MicrojsonBinaryModel +__version__ = "0.1.10" diff --git a/src/microjson/tile.py b/src/microjson/tile.py new file mode 100644 index 0000000..83daebf --- /dev/null +++ b/src/microjson/tile.py @@ -0,0 +1,41 @@ +from typing import List, Optional, Union, Dict +from pydantic import BaseModel, AnyUrl, conlist, RootModel +from pathlib import Path + + +class TileLayer(BaseModel): + id: str + fields: Union[None, Dict[str, str], Dict[str, Dict]] + minzoom: Optional[int] = None + maxzoom: Optional[int] = None + description: Optional[str] = None + + +class TileModel(BaseModel): + tilejson: str + tiles: List[Union[Path, AnyUrl]] + name: Optional[str] = None + description: Optional[str] = None + version: Optional[str] = None + attribution: Optional[str] = None + template: Optional[str] = None + legend: Optional[str] = None + scheme: Optional[str] = None + grids: Optional[Union[Path, AnyUrl]] = None + data: Optional[Union[Path, AnyUrl]] = None + minzoom: Optional[int] = None + maxzoom: Optional[int] = None + bounds: Optional[conlist( # type: ignore + float, + min_length=4, + max_length=10)] = None + center: Optional[conlist( # type: ignore + float, + min_length=3, + max_length=6)] = None + fillzoom: Optional[int] = None + vector_layers: List[TileLayer] + + +class TileJSON(RootModel): + root: TileModel diff --git a/tests/json/tilejson/invalid/no_tilejson.json b/tests/json/tilejson/invalid/no_tilejson.json new file mode 100644 index 0000000..27ec336 --- /dev/null +++ b/tests/json/tilejson/invalid/no_tilejson.json @@ -0,0 +1,35 @@ +{ + "name": "2D Data Example", + "description": "A tileset showing 2D data with multiple layers of detail.", + "version": "1.0.0", + "attribution": "Example", + "tiles": [ + "http://example.com/tiles/{zlvl}/{x}/{y}.json" + ], + "minzoom": 0, + "maxzoom": 18, + "bounds": [0, 0, 24000, 24000], + "center": [12000, 12000, 10], + "format": "json", + "vector_layers": [ + { + "id": "image_layer", + "description": "Image layer", + "minzoom": 0, + "maxzoom": 18, + "fields": { + "string": { + "Plate": "Plate ID", + "Image": "Image URI" + }, + "numerical": { + "X": "X coordinate", + "Y": "Y coordinate", + "Channel": "Channel ID" + } + } + } + ], + "fillzoom": 6, + "legend": "http://example.com/legend.png" +} \ No newline at end of file diff --git a/tests/json/tilejson/invalid/no_vector_layer.json b/tests/json/tilejson/invalid/no_vector_layer.json new file mode 100644 index 0000000..87d4928 --- /dev/null +++ b/tests/json/tilejson/invalid/no_vector_layer.json @@ -0,0 +1,17 @@ +{ + "tilejson": "3.0.0", + "name": "2D Data Example", + "description": "A tileset showing 2D data with multiple layers of detail.", + "version": "1.0.0", + "attribution": "Example", + "tiles": [ + "http://example.com/tiles/{zlvl}/{x}/{y}.json" + ], + "minzoom": 0, + "maxzoom": 18, + "bounds": [0, 0, 24000, 24000], + "center": [12000, 12000, 10], + "format": "json", + "fillzoom": 6, + "legend": "http://example.com/legend.png" +} \ No newline at end of file diff --git a/tests/json/tilejson/valid/fileurl.json b/tests/json/tilejson/valid/fileurl.json new file mode 100644 index 0000000..18df111 --- /dev/null +++ b/tests/json/tilejson/valid/fileurl.json @@ -0,0 +1,33 @@ +{ + "tilejson": "3.0.0", + "name": "2D Data Example", + "description": "A tileset showing 2D data with multiple layers of detail.", + "version": "1.0.0", + "attribution": "Example", + "tiles": [ + "./tiled_example/{zlvl}/{x}/{y}.json" + ], + "minzoom": 0, + "maxzoom": 10, + "bounds": [0, 0, 24000, 24000], + "center": [12000, 12000, 0], + "vector_layers": [ + { + "id": "image_layer", + "description": "Image layer", + "minzoom": 0, + "maxzoom": 10, + "fields": { + "string": { + "Plate": "Plate ID", + "Image": "Image URI" + }, + "numerical": { + "Label": "Label ID", + "Channel": "Channel ID" + } + } + } + ], + "fillzoom": 3 +} \ No newline at end of file diff --git a/tests/json/tilejson/valid/microjson_tileset.json b/tests/json/tilejson/valid/microjson_tileset.json new file mode 100644 index 0000000..cc634b3 --- /dev/null +++ b/tests/json/tilejson/valid/microjson_tileset.json @@ -0,0 +1,33 @@ +{ + "tilejson": "3.0.0", + "name": "2D Data Example", + "description": "A tileset showing 2D data with multiple layers of detail.", + "version": "1.0.0", + "attribution": "Example", + "tiles": [ + "http://example.com/tiled_example/{zlvl}/{x}/{y}.json" + ], + "minzoom": 0, + "maxzoom": 10, + "bounds": [0, 0, 24000, 24000], + "center": [12000, 12000, 0], + "vector_layers": [ + { + "id": "image_layer", + "description": "Image layer", + "minzoom": 0, + "maxzoom": 10, + "fields": { + "string": { + "Plate": "Plate ID", + "Image": "Image URI" + }, + "numerical": { + "Label": "Label ID", + "Channel": "Channel ID" + } + } + } + ], + "fillzoom": 3 +} \ No newline at end of file diff --git a/tests/json/tilejson/valid/minimal.json b/tests/json/tilejson/valid/minimal.json new file mode 100644 index 0000000..75d7c0e --- /dev/null +++ b/tests/json/tilejson/valid/minimal.json @@ -0,0 +1,14 @@ +{ + "tilejson": "3.0.0", + "tiles": [ + "http://localhost:8080/tiles/{zlvl}/{x}/{y}.json" + ], + "vector_layers": [ + { + "id": "cells", + "fields": { + "name": "string" + } + } + ] +} \ No newline at end of file diff --git a/tests/json/tilejson/valid/osm.json b/tests/json/tilejson/valid/osm.json new file mode 100644 index 0000000..6444a1e --- /dev/null +++ b/tests/json/tilejson/valid/osm.json @@ -0,0 +1,42 @@ +{ + "tilejson": "3.0.0", + "name": "OpenStreetMap", + "description": "A free editable map of the whole world.", + "version": "1.0.0", + "attribution": "(c) OpenStreetMap contributors, CC-BY-SA", + "scheme": "xyz", + "tiles": [ + "https://a.tile.custom-osm-tiles.org/{z}/{x}/{y}.mvt", + "https://b.tile.custom-osm-tiles.org/{z}/{x}/{y}.mvt", + "https://c.tile.custom-osm-tiles.org/{z}/{x}/{y}.mvt" + ], + "minzoom": 0, + "maxzoom": 18, + "bounds": [ -180, -85, 180, 85 ], + "fillzoom": 6, + "something_custom": "this is my unique field", + "vector_layers": [ + { + "id": "telephone", + "fields": { + "phone_number": "the phone number", + "payment": "how to pay" + } + }, + { + "id": "bicycle_parking", + "fields": { + "type": "the type of bike parking", + "year_installed": "the year the bike parking was installed" + } + }, + { + "id": "showers", + "fields": { + "water_temperature": "the maximum water temperature", + "wear_sandles": "whether you should wear sandles or not", + "wheelchair": "is the shower wheelchair friendly?" + } + } + ] +} \ No newline at end of file diff --git a/tests/test_tilejson.py b/tests/test_tilejson.py new file mode 100644 index 0000000..1c1538f --- /dev/null +++ b/tests/test_tilejson.py @@ -0,0 +1,57 @@ +import json +import pytest +from pydantic import ValidationError +from microjson.tile import TileJSON +from microjson.utils import gather_example_files + + +# Define the directories containing the example JSON files +VALID_EXAMPLES_DIR = "tests/json/tilejson/valid" +INVALID_EXAMPLES_DIR = "tests/json/tilejson/invalid" + + +valid_examples = gather_example_files(VALID_EXAMPLES_DIR) +invalid_examples = gather_example_files(INVALID_EXAMPLES_DIR) + + +@pytest.mark.parametrize("filename", valid_examples) +def test_valid_tilejson(filename): + with open(filename, "r") as f: + data = json.load(f, strict=True) + + # Try to parse the data as a TileJSON object + try: + _ = TileJSON.model_validate(data) + except ValidationError as e: + pytest.fail( + f"""ValidationError occurred + during validation of {filename}: {str(e)}""" + ) + except Exception as e: + pytest.fail( + f"""Unexpected error occurred + during validation of {filename}: {str(e)}""" + ) + + +@pytest.mark.parametrize("filename", invalid_examples) +def test_invalid_tilejson(filename): + with open(filename, "r") as f: + data = json.load(f, strict=True) + + # This will raise a ValidationError if the data does not match the TileJSON + try: + _ = TileJSON.model_validate(data) + pytest.fail( + f"""Parsing succeeded on {filename}, + but it should not have.""" + ) + except ValidationError: + # The validation error is expected, so we just pass + pass + except Exception as e: + # An unexpected error occurred, so we fail the test + pytest.fail( + f"""Unexpected error occurred + during validation of {filename}: {str(e)}""" + )