Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
137 commits
Select commit Hold shift + click to select a range
2dcf57d
feat: integer polygons with conversion to floating-point
DvBree Jul 8, 2026
8d9ac55
feat: copy pbf encoding functionality from go-spatial
DvBree Jul 8, 2026
6abcd3f
refactor: remove unused parameter in pbf encoding functionality
DvBree Jul 8, 2026
f42a4a6
feat: encode polygon in quadrant
DvBree Jul 8, 2026
e96c81c
fix: prepare geo instead of just translation
DvBree Jul 8, 2026
9004142
feat: primitive detection of relevant tiles
DvBree Jul 8, 2026
a949286
chore: adapt to slippy api change
DvBree Jul 9, 2026
d45a6c6
feat: create tables for encoded features
DvBree Jul 9, 2026
3f72663
fix: encode arbitrary geometries
DvBree Jul 9, 2026
995f82b
fix: QBBox returns slice
DvBree Jul 10, 2026
0b60cd9
refactor: decouple geometry logic, logging and channel logic
DvBree Jul 10, 2026
fd63764
feat: snap meta-results available during processing
DvBree Jul 10, 2026
c2a67cf
fix: create geometry_type field in encodedtable
DvBree Jul 10, 2026
ac8917d
feat: types for encoded data
DvBree Jul 13, 2026
13c1944
feat: encoded geometries to database layer
DvBree Jul 13, 2026
b462c23
feat: write encoded geometries to target geopackage
DvBree Jul 13, 2026
b1057e8
fix: comma in SQL statement
DvBree Jul 13, 2026
f4e807b
fix: wrong mix of initialize and append to slice
DvBree Jul 13, 2026
ad77fc4
fix: SQL statement syntax error
DvBree Jul 13, 2026
8b52d84
fix: serialize int slice for storage
DvBree Jul 14, 2026
e9f1d61
fix: update snap test with new snap return construct
DvBree Jul 14, 2026
29898a2
refactor: moved helper function to geomhelp
DvBree Jul 14, 2026
1dfe343
feat: flag for optional encoding of geometries
DvBree Jul 14, 2026
bb69eac
chore: linter warnings (mainly in copied code)
DvBree Jul 14, 2026
5e4c758
refactor: removed unused code
DvBree Jul 14, 2026
7bb1c18
refactor: remove unused function
DvBree Jul 17, 2026
4e07861
test: add PolygonSliceToGeom unit test
DvBree Jul 17, 2026
ee63113
feat: recover go-spatial attribute dictionary building func
DvBree Jul 20, 2026
c877203
feat: read fids based on tile coords
DvBree Jul 20, 2026
23ceeb7
feat: retrieve feature attributes based on fid list
DvBree Jul 20, 2026
b150751
feat: extract info from data structure for a single feature
DvBree Jul 20, 2026
4688d93
refactor: name for internal representation attribute table
DvBree Jul 20, 2026
aa636f6
feat: construct keys dictionary
DvBree Jul 20, 2026
b85a6d6
feat: generate value dictionary
DvBree Jul 20, 2026
1d688ab
feat: construct feature struct from go-spatial
DvBree Jul 20, 2026
0fb1880
feat: encode data into layer
DvBree Jul 20, 2026
53bcb65
feat: wire functionality to encode a single table
DvBree Jul 20, 2026
4ddebe2
feat: add 'texel mvt' command for tiling
DvBree Jul 20, 2026
7ddb79a
refactor: isolate go-spatial types
DvBree Jul 21, 2026
2a73ee4
feat: add overflow guard
DvBree Jul 21, 2026
b5110c4
refactor: removed unused code copied from go-spatial
DvBree Jul 21, 2026
4d06364
refactor: rename file for clarity
DvBree Jul 21, 2026
0a4e325
refactor: move code into central file
DvBree Jul 21, 2026
5293633
refactor: cleaner separation processing and encoding
DvBree Jul 24, 2026
7a533fa
refactor: align with protobuf specs
DvBree Jul 24, 2026
337597d
test: tile encoding
DvBree Jul 24, 2026
2c8336c
fix: encoded table creation with flag
DvBree Jul 27, 2026
f147642
fix: incorrect index in tile selection
DvBree Jul 27, 2026
67dd1f0
refactor: separate snap and mvt commands
DvBree Jul 27, 2026
bc6e7b8
test: new processGeometry test
DvBree Jul 28, 2026
a5b8f26
docs: update comments in source
DvBree Jul 28, 2026
c346fb6
chore: remove trailing whitespace
DvBree Jul 28, 2026
33bbfe5
docs: clear up file descriptions
DvBree Aug 4, 2026
2bfeb95
refactor: moved function to mapslichelp
DvBree Aug 4, 2026
b0e4504
feat: bbox tile detection with buffer
DvBree Aug 3, 2026
d1b48eb
test: tile detection
DvBree Aug 3, 2026
aca0a55
feat: lineTrace across tiles
DvBree Aug 3, 2026
42f6583
fix: incorrect index, bugs persist (new wrong test cases)
DvBree Aug 4, 2026
1c1d489
fix: choose internal pixel level in line tracing
DvBree Aug 5, 2026
b9856db
feat: raycast to detect outside tiles
DvBree Aug 5, 2026
2dbc373
chore: lint
DvBree Aug 5, 2026
09f3929
fix: various issues in tile detection
DvBree Aug 6, 2026
76d270b
Merge pull request #43 from PDOK/PDOK-18835/vervanging-trex-v2-attrib…
DvBree Aug 6, 2026
5078ecf
feat: new Tile type for plumbing
DvBree Aug 10, 2026
b372c4d
feat: buffer options as flags
DvBree Aug 10, 2026
e26800b
feat: default encoding for tiles contained in polygon
DvBree Aug 10, 2026
5a6bb5e
feat: wiring for default geometry
DvBree Aug 10, 2026
6d6eb7e
fix: buffer for default space-filling tile
DvBree Aug 11, 2026
54b3e70
fix: correct tile detection for large geometries
DvBree Aug 11, 2026
8cafc49
chore: linter warnings
DvBree Aug 11, 2026
ae20ee1
fix: invert y coordinates
DvBree Aug 11, 2026
6a54630
docs: descriptive code comments
DvBree Aug 11, 2026
aa7baeb
Merge pull request #44 from PDOK/PDOK-18835/vervanging-trex-v2-tile-d…
DvBree Aug 20, 2026
16f161e
feat: z coord in encodedtable and zoomlevel
DvBree Aug 11, 2026
5eb4caf
feat: read toml config and process it
DvBree Aug 12, 2026
4d43ab5
feat: list tiles
DvBree Aug 13, 2026
9154cfb
feat: processing logic for multiple layers
DvBree Aug 13, 2026
9cb5688
feat: wire config and layer processing in main
DvBree Aug 13, 2026
2902a75
fix: file extension to pbf
DvBree Aug 14, 2026
e027dd4
refactor: split long buildLayers function
DvBree Aug 14, 2026
894dea6
feat: logging tile process
DvBree Aug 17, 2026
3034673
Merge pull request #45 from PDOK/PDOK-18835/vervanging-trex-v2-merge-…
DvBree Aug 21, 2026
15716d1
feat: line tracing for lines
DvBree Aug 18, 2026
82b61e0
refactor: improve registerFunc interface for non-polygons
DvBree Aug 18, 2026
d8ecc8d
feat+refactor: general linetrace function + reorganise line tracing
DvBree Aug 19, 2026
0be3179
refactor: map to slice helper
DvBree Aug 19, 2026
66a4a02
feat: insert general geometries in pointindex
DvBree Aug 19, 2026
f88f096
feat: first version with lines
DvBree Aug 20, 2026
4f75d40
fix: snap tests
DvBree Aug 21, 2026
1f6b457
test: first processing tests
DvBree Aug 21, 2026
cf0257c
refactor: organize processing
DvBree Aug 21, 2026
f2b064b
refactor: remove unused class
DvBree Aug 21, 2026
07285e1
feat: extend merge to all geometries
DvBree Aug 24, 2026
5624161
tests: MergeGeometries
DvBree Aug 24, 2026
15520a5
refactor: distinction tile and pixel level
DvBree Aug 24, 2026
0c51f74
chore: lint caseorder
DvBree Aug 24, 2026
2987c93
refactor: remove functional dependency that clarifies tile/pixel dist…
DvBree Aug 24, 2026
bdf043c
refactor: pointindex interface for logic decoupling
DvBree Aug 24, 2026
2666093
tests: processing tests
DvBree Aug 24, 2026
ce50dac
refactor: separate tile detection and index functions
DvBree Aug 24, 2026
dbc90d1
fix: geometry slice to multigeometry
DvBree Aug 25, 2026
d3ffb30
fix: no panic on geometries that degenerate to points and lines
DvBree Aug 25, 2026
fe877b0
fix: duplicate tiles with multigeometries
DvBree Aug 25, 2026
ee288eb
fix: inverted y coordinate for bbox tile detection
DvBree Aug 25, 2026
3f6f807
fix: incorrect parameter caused huge buffer with lineTraceLine
DvBree Aug 25, 2026
336f95b
testfix: fix left-up vs left-down origin conflict in tests
DvBree Aug 28, 2026
a9e3227
refactor: remove unnecessary wrapper
DvBree Sep 8, 2026
e17aae5
feat: compression of tiles
DvBree Sep 10, 2026
ecd84a3
Merge pull request #47 from PDOK/PDOK-18835/vervanging-trex-v2-points…
DvBree Sep 11, 2026
ae2212f
Merge pull request #48 from PDOK/PDOK-18835/vervanging-trex-v2-compre…
DvBree Sep 11, 2026
4349bae
fix: exit early with nil geometry
DvBree Sep 11, 2026
c68277e
test: processSingle nil result after snapping
DvBree Sep 11, 2026
427c0ef
Merge pull request #49 from PDOK/PDOK-18835/vervanging-trex-v2-exit-w…
DvBree Sep 18, 2026
91cf1f7
feat: add tile filter function (first version)
DvBree Sep 18, 2026
d3fa2d7
feat: clip flag and use in config
DvBree Sep 18, 2026
eeccd4b
refactor: more descriptive function name
DvBree Sep 18, 2026
455c6b5
fix: sign error
DvBree Sep 18, 2026
cd8d946
test: test filter tiles
DvBree Sep 18, 2026
e06efb8
chore/refactor: decrease function complexity
DvBree Sep 18, 2026
ae316eb
fix: round floats with floor/ceil instead of truncation
DvBree Sep 25, 2026
3c67301
Merge pull request #50 from PDOK/PDOK-18835/vervanging-trex-v2-clip-info
DvBree Sep 25, 2026
e378519
Merge pull request #51 from PDOK/PDOK-18835/vervanging-trex-v2-roundi…
DvBree Sep 25, 2026
5f4bf7a
feat: fail if tileset not uniquely determined
DvBree Oct 1, 2026
60f2622
feat: read output config from toml
DvBree Oct 1, 2026
4940af0
feat: write to azure blobstore and/or file system
DvBree Oct 2, 2026
b607b8c
test: test tile writing
DvBree Oct 5, 2026
5a2d37e
refactor: more abstract writer for easier testing
DvBree Oct 5, 2026
aae3140
test: writer construction
DvBree Oct 5, 2026
0f3e4a7
feat/refactor: enforce at most one tileset
DvBree Oct 5, 2026
d5cf7c9
chore: go mod tidy
DvBree Oct 5, 2026
afdc32d
Merge pull request #52 from PDOK/PDOK-18835/vervanging-trex-v2-target…
DvBree Oct 5, 2026
a631abf
fix: remove unused fields
DvBree Oct 7, 2026
495216a
feat: replace env vars with their values in TOML config
DvBree Oct 7, 2026
22875da
docs: updated documentation for new texel
DvBree Oct 8, 2026
eb3ae75
Merge pull request #53 from PDOK/PDOK-18835/vervanging-trex-v2-env-vars
DvBree Oct 9, 2026
e5a4247
fix: do not fail when config layers are missing in gpkg
DvBree Oct 19, 2026
d2a6cb3
Merge pull request #54 from PDOK/PDOK-18835/vervanging-trex-v2-missin…
DvBree Oct 20, 2026
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
225 changes: 176 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,76 +7,60 @@ Card](https://goreportcard.com/badge/PDOK/texel)](https://goreportcard.com/repor
[![Docker
Pulls](https://img.shields.io/docker/pulls/pdok/texel.svg)](https://hub.docker.com/r/pdok/texel)

Preprocesses a [GeoPackage](https://www.geopackage.org/)
for creating _valid_ (more info below) vector tiles from it.
(And in the future probably will also create the actual vector tiles.)

The (MULTI)POLYGON geometries in a geopackage are snapped to the grid cq matrix
matching the one of the intended (internal pixel grid of the) vector tiles.
While ensuring that no (new) intersections arise from this, by adding points
to lines that skim along other vertices.
An additional benefit is that the vector tile generator needs to process fewer
data (snapping combines some points into one) and does not need any additional
processing aside from clipping and converting to the internal tile pixel grid.
Processes a [GeoPackage](https://www.geopackage.org/) in order to produce Mapbox vector tiles.

## "Valid" geometries

Our definition of _valid_ is in the context of vector tiles and is more loose
than the [OGC rules](https://en.wikipedia.org/wiki/Simple_Features).
Mainly in comparison to the OGC rules, we allow:

* Overlap (not intersection)
* Linear rings or line strings with less than 3 vertices
(effectively becoming lines or points)

With the drawing/rendering of vector tiles in mind, these invalidities do not
cause a problem. On the contrary, they introduce possibilities.
E.g. when zooming out, you would like to still see a lake as a point and a
river as a line. (The tool that this codebase is forked from, `sieve` would
filter those out.) Also when zooming out on a lake with an attached river,
you would like to keep seeing that river as a line, but still as part of the
original geometry/feature including all attributes and styling.
This tool was originally created to snap MULTI(POLYGONS) to the grid cq matrix
corresponding to the vector tiles, while preserving a weak notion of _valid_
(more info below). Subsequently, the tool can be used in two ways:
- `texel snap`, which snaps the polygons.
- `texel snap -enc` with `texel mvt`. The first command snaps and encodes
geometries according to the standard. The second assembles the tiles.

## Examples

Not adding extra points could create intersections:

![snapped with vs without extra point](./images/snapped-with-vs-without-extra-point.png)

Close thin polygons can turn into lines:

![before.png](./images/before.png) ![after-overlapping-lines.png](./images/after-overlapping-lines.png)
## Usage

## Notes
Getting help:

- It will take a Geopackage and writes a new Geopackage with all the
(MULTI)POLYGON tables preprocessed.
- All other spatial tables are 'untouched' and copied as-is.
- Other non-spatial tables are not copied to the new geopackage.
- :warning: Spatialite lib is mandatory for running this application. This lib is needed for
creating the RTree triggers on the spatial tables for updating/maintaining the
RTree.
```sh
./texel --help
./texel snap --help
./texel mvt --help
```

## Usage
Snap and encode geopackage

```sh
./texel -s=[source GPKG] -t=[target GPKG] \
./texel snap \
-s=[source GPKG] -t=[target GPKG] \
-tms=[tile matrix set for filtering] -z=[tile matrix ids] \
-p=[pagesize for writing to target GPKG] -o=[overwrite target GPKG] \
-pl=[keep points and lines]
-pl=[keep points and lines] \
-iog=[ignore outside grid] --rwo=[reverse winding order] \
-enc=[encode geometries] \
-buf=[buffer size for tile detection] \
-lt=[line trace method for tile detection] \
-c=[current clip]
```

./texel --help
Make tiles from snapped output

```sh
./texel mvt \
-c=[config toml file] \
-z=[tile matrix id]
```

### Docker

Example Docker command for running texel without encoding geometries.

```docker
docker run \
--name texel \
--rm \
-u $(id -u):$(id -g) \
-v `pwd`/example:/example \
pdok/texel \
snap
-s=./example/example.gpkg \
-t=./example/example-processed.gpkg \
-tms="NetherlandsRDNewQuad" \
Expand All @@ -100,9 +84,152 @@ go build .
docker build -t pdok/texel .
```

:warning: Spatialite lib is mandatory for running this application. This lib is
needed for creating the RTree triggers on the spatial tables for
updating/maintaining the RTree.

## Commands

### Snapping

`texel snap` takes a geopackage, snaps all (MULTI)POLYGON geometries in it, and
writes to a new geopackage. If encoding is enabled, for each table it also
creates `{TABLENAME}_encoded` tables. A row in such a table is a geometry
encoded for a particular tile. These are required input for `texel mvt`
(explained below).

- All other spatial tables are 'untouched' and copied as-is.
- Other non-spatial tables are not copied to the new geopackage.

Input parameters:
- source (`-s`): source Geopackage.
- target (`-t`): target Geopackage. If specified as `target.gpkg`, output is
written to `target_{zoomlevel}.gpkg` for each zoomlevel.
- tile matrix set (`-tms`): tile matrix set name. Supported tile matrix sets can
be found in the `tms20/tilematrixsets` folder.
- tile matrix ids (`-z`): JSON array of integers of zoomlevels to target.

Snap behaviour parameters:
- keep points and lines: (`-pl`): if (part of) a polygon degenerates to points
and lines during snapping remove those parts.
- ignore outside grid (`-iog`): skip geometries outside of the grid
- reverse winding order (`-rwo`): reverse winding order of geometries. This is
used to comply with the vectortile standard.

Encoding behaviour parameters:
- encode (`-enc`): enable encoding. If set to false, do not create the
`{TABLENAME}_encoded` tables.
- line trace (`-lt`): there are two algorithms to determine on which tiles a
geometry lies. When `-lt=false`, use a bounding box. When `-lt=true`, trace
border of features. This is more precise, but more computationally expensive.
- buffer (`-buf`): buffer size around each tile. Geometries within this buffer
are encoded for this tile.
- clip (`-c`): a higher-level tile written as `[z, x, y]`, for zoomlevel and
coordinates. It is required that this `z` not larger than the
`tile-matrix-ids` above. Only encode geometries lying within this
higher-level tile.

The `clip` parameter might require more explanation. One possible workflow is
divide a workload of generating all tiles into several "clips", for example for
parallel processing. A clip is such a tile at a higher level. In many
instances, for example when the buffer is strictly positive, the standard
workflow of `texel snap` + `texel mvt` generates tiles neighbouring the clip.
This option prevents that.

## Tile generation

The output of `texel snap -enc` is input for `texel mvt`, which generates
vectortiles. There are only a few parameters

- config (`-c`): a TOML configuration file that is detailed below.
- tile matrix id (`-z`): the zoomlevel that is being targeted.

Almost all configuration is determined in this TOML file. The configuration is
similar to configurations for
[trex](https://github.com/t-rex-tileserver/t-rex), however, valid
configurations for `trex` are not valid for `texel` in a way detailed below.
The following configuration is an example.

```toml
[tileset]
name = "NetherlandsRDNewQuad"

[[datasource]]
name = "brt"
path = "texeled/brt_12.gpkg"

[[tileset.layer]]
name = "wegdeel"
datasource = "brt"
table_name = "wegdeel_12"
minzoom = 12
maxzoom = 12

[[tileset.layer]]
name = "waterdeel"
datasource = "brt"
table_name = "waterdeel_11_12"
minzoom = 11
maxzoom = 12

[cache.file]
base = "/data"

[cache.azure]
connection_string = "{{env.AZURE_CONNECTION_STRING}}"
container = "{{env.AZURE_CONTAINER}}"
prefix_key = "{{env.AZURE_KEY_PREFIX}}"
```

Notes:
- A `[[datasource]]` is a geopackage that is results from `texel snap -enc`.
- A `[[tileset.layer]]` represents both a table in a geopackage and a layer in
the resulting vectortile. The geopackage and table is determined by
`datasource`, which refers to the name of a datasource and `table_name`
(which should NOT inclde an `_encoded` suffix).

A layer is included if the parameter `-z` specified above lies between
`minzoom` and `maxzoom` (inclusive).
- If `[cache.file]` is specified, tiles are written with that path as base.
- If `[cache.azure]` is specified, write to the an azure blobstore.
- At least one of `[cache.file]` or `[cache.azure]` needs to be specified.
- At the destination, the tiles are written as `{NAME}/{z}/{x}/{y}.pbf`. Here
`name` is the name of the tileset. When writing to the azure blobstore, the
`prefix_key` is also prepended.

## "Valid" geometries

`texel snap` produces "valid" geometries.

Our definition of _valid_ is in the context of vector tiles and is more loose
than the [OGC rules](https://en.wikipedia.org/wiki/Simple_Features).
Mainly in comparison to the OGC rules, we allow:

* Overlap (not intersection)
* Linear rings or line strings with less than 3 vertices
(effectively becoming lines or points)
* Gaps

With the drawing/rendering of vector tiles in mind, these invalidities do not
cause a problem. On the contrary, they introduce possibilities.
E.g. when zooming out, you would like to still see a lake as a point and a
river as a line. (The tool that this codebase is forked from, `sieve` would
filter those out.) Also when zooming out on a lake with an attached river,
you would like to keep seeing that river as a line, but still as part of the
original geometry/feature including all attributes and styling.

### Examples

Not adding extra points could create intersections:

![snapped with vs without extra point](./images/snapped-with-vs-without-extra-point.png)

Close thin polygons can turn into lines:

![before.png](./images/before.png) ![after-overlapping-lines.png](./images/after-overlapping-lines.png)

## References

* [sieve](https://github.com/pdok/sieve) is the predecessor of this tool
and much of its processing codebase is reused as boilerplate.

101 changes: 101 additions & 0 deletions config/mvtconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package config

import (
"fmt"
"os"
"regexp"

"github.com/BurntSushi/toml"
)

type DataSource struct {
Name string `toml:"name"`
Path string `toml:"path"`
}

type LayerConfig struct {
Name string `toml:"name"`
MinZoom uint `toml:"minzoom"`
MaxZoom uint `toml:"maxzoom"`
DataSource string `toml:"datasource"`
TableName string `toml:"table_name"`
}

// Tileset mirrors a `[[Tileset]]` array-of-tables entry, including its
// nested `[[Tileset.layer]]` entries.
type Tileset struct {
Name string `toml:"name"`
Layer []LayerConfig `toml:"layer"`
}

type AzureConfig struct {
ConnectionString string `toml:"connection_string"`
Container string `toml:"container"`
PrefixKey string `toml:"prefix_key"`
}

type FileConfig struct {
Base string `toml:"base"`
}

// In case S3 will be needed in the future, these are the old `t_rex` configs.
// type S3Config struct {
// Endpoint string `toml:"endpoint"`
// Bucket string `toml:"bucket"`
// AccessKey string `toml:"access_key"`
// SecretKey string `toml:"secret_key"`
// Region string `toml:"region"`
// KeyPrefix string `toml:"key_prefix"`
// }

type CacheConfig struct {
File *FileConfig `toml:"file"`
Azure *AzureConfig `toml:"azure"`
}

// TomlConfig mirrors the top-level structure of an mvt config toml file,
// e.g. example/NetherlandsRDNewQuad.toml.
type TomlConfig struct {
DataSource []DataSource `toml:"datasource"`
Tileset *Tileset `toml:"tileset"`
Cache CacheConfig `toml:"cache"`
}

// Replace {{env.NAME}} by value of environment variable NAME. Inserted
// verbatim. Unset variables result in an error.
func substituteEnv(s string) (string, error) {
envPlaceholder := regexp.MustCompile(
`\{\{\s*env\.([A-Za-z_][A-Za-z0-9_]*)\s*\}\}`)

var missing []string
out := envPlaceholder.ReplaceAllStringFunc(s, func(m string) string {
name := envPlaceholder.FindStringSubmatch(m)[1]
val, ok := os.LookupEnv(name)
if !ok {
missing = append(missing, name)
}
return val
})
if len(missing) > 0 {
return "", fmt.Errorf("unset environment variables: %v", missing)
}
return out, nil
}

// ParseMVTConfig reads an mvt config toml file, substitutes {{env.NAME}}
// placeholders and returns the resulting TomlConfig.
func ParseMVTConfig(path string) (TomlConfig, error) {
var cfg TomlConfig
raw, err := os.ReadFile(path)
if err != nil {
return TomlConfig{}, fmt.Errorf("reading mvt config %q: %w", path, err)
}
text, err := substituteEnv(string(raw))
if err != nil {
return TomlConfig{}, fmt.Errorf("mvt config %q: %w", path, err)
}
if _, err := toml.Decode(text, &cfg); err != nil {
return TomlConfig{}, fmt.Errorf("decoding mvt config %q: %w", path, err)
}
return cfg, nil
}
Loading
Loading