Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#25: Filter by property #32

Merged
merged 6 commits into from
Sep 9, 2024
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
150 changes: 138 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@

# MVTTools

MapLibre/Mapbox vector tiles (MVT) reader/writer library for Swift, together with a tool for working with vector tiles from the command line.
MapLibre/Mapbox vector tiles (MVT) reader/writer library for Swift, together with a powerful tool for working with vector tiles and GeoJSONs from the command line.

## Features

- Load and write Mapnik Vector Tiles from/to disk, data objects or URLs (also handles gzipped input)
- Export options: Zipped, buffered (in pixels or extents), simplified (in meters or extents)
- Can dump a tile as a GeoJSON object
- Supported projections: EPSG:4326, EPSG:3857 or none (uses the tile's coordinate space)
- Fast search (supports indexing), either within a bounding box or with center and radius
- Extract selected layers into a new tile
- Merge two tiles into one
- Load and write MapLibre/Mapbox Vector Tiles from/to disk, data objects or URLs (also handles gzipped input).
- Export options: Zipped, buffered (in pixels or extents), simplified (in meters or extents).
- Can dump a tile as a GeoJSON object.
- Supported projections: EPSG:4326, EPSG:3857 or none (uses the tile's coordinate space).
- Fast search (supports indexing), either within a bounding box or with center and radius.
- Extract selected layers into a new tile.
- Merge tiles into one.
- Can extract some infos from tiles like feature count, etc.
- Powerful command line tool (via [Homebrew](#command-line-tool), documentation below) for working with vector tiles and GeoJSON files
- Powerful command line tool (via [Homebrew](#command-line-tool), documentation below) for working with vector tiles and GeoJSON files.

## Requirements

Expand Down Expand Up @@ -176,7 +176,10 @@ mvt dump Tests/MVTToolsTests/TestData/14_8716_8015.vector.mvt
---
### mvt info

Print some informations about vector tiles/GeoJSONs.
Print some informations about vector tiles/GeoJSONs:
- The number of features, points, linestrings, polygons per layer
- The properties for each layer
- Counts of specific properties

**Example 1**: Print information about the MVTTools test vector tile at zoom 14, at Yaoundé, Cameroon.

Expand Down Expand Up @@ -207,10 +210,37 @@ mvt info https://demotiles.maplibre.org/tiles/2/2/1.pbf
countries | 113 | 0 | 0 | 113 | 0 | 2
geolines | 4 | 0 | 4 | 0 | 0 | 2
```
---

**Example 3**: Print information about the properties for each layer.

```bash
mvt info Tests/MVTToolsTests/TestData/14_8716_8015.vector.mvt

Name | area | class | group | layer | ldir | len | name | name_de | name_en | name_es | name_fr | network | oneway | ref | reflen | scalerank | type
--------------------+------+-------+-------+-------+------+-----+------+---------+---------+---------+---------+---------+--------+-----+--------+-----------+-----
airport_label | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
area_label | 55 | 55 | 0 | 0 | 0 | 0 | 55 | 55 | 55 | 55 | 55 | 0 | 0 | 0 | 0 | 0 | 0
barrier_line | 0 | 4219 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
bridge | 0 | 14 | 0 | 13 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 14 | 0 | 0 | 0 | 0
...
```
---

**Example 4**: Print information about specific properties.

```bash
mvt info -p class Tests/MVTToolsTests/TestData/14_8716_8015.vector.mvt

Name | cemetery | driveway | fence | hedge | hospital | industrial | main | major_rail | mini_roundabout | minor_rail | motorway | park | parking | path | pitch | rail | school | service | street | street_limited | wetland | wood
-------+----------+----------+-------+-------+----------+------------+------+------------+-----------------+------------+----------+------+---------+------+-------+------+--------+---------+--------+----------------+---------+-----
class | 4 | 36 | 3895 | 324 | 9 | 2 | 113 | 21 | 1 | 13 | 30 | 95 | 59 | 46 | 21 | 2 | 59 | 187 | 376 | 4 | 4 | 12
```

---
### mvt query

Query a vector tile or GeoJSON file with a search term.
**Example 1**: Query a vector tile or GeoJSON file with a search term.

```bash
mvt query Tests/MVTToolsTests/TestData/14_8716_8015.vector.mvt "École"
Expand Down Expand Up @@ -246,7 +276,7 @@ mvt query Tests/MVTToolsTests/TestData/14_8716_8015.vector.mvt "École"
}
```
---
Query a tile with `latitude,longitude,radius`.
**Example 2**: Query a tile with `latitude,longitude,radius`.

```bash
mvt query Tests/MVTToolsTests/TestData/14_8716_8015.geojson "3.87324,11.53731,1000"
Expand Down Expand Up @@ -276,6 +306,102 @@ mvt query Tests/MVTToolsTests/TestData/14_8716_8015.geojson "3.87324,11.53731,10
...
}
```
---
**Example 3**: Query a tile for properties.

```bash
mvt query -p Tests/MVTToolsTests/TestData/14_8716_8015.vector.mvt ".area > 40000 and .class == 'hospital'"

{
"features" : [
{
"bbox" : [
11.510410308837876,
3.871287406415171,
11.510410308837876,
3.871287406415171
],
"geometry" : {
"coordinates" : [
11.510410308837876,
3.871287406415171
],
"type" : "Point"
},
"id" : 2,
"properties" : {
"area" : 48364.9375,
"class" : "hospital",
"name" : "Hopital Central de Yaoundé",
"name_de" : "Hopital Central de Yaoundé",
"name_en" : "Hopital Central de Yaoundé",
"name_es" : "Hopital Central de Yaoundé",
"name_fr" : "Hopital Central de Yaoundé",
"vt_layer" : "area_label"
},
"type" : "Feature"
}
],
"type" : "FeatureCollection"
}
```

The query language is loosely modeled after the jq query language. Here is an overview.

Example:
```
"properties": {
"foo": {"bar": 1},
"some": ["a", "b"],
"value": 1,
"string": "Some name"
}
```

Values are retrieved by putting a `.` in front of the property name. The property name must be quoted
if it is a number or contains any non-alphabetic characters. Elements in arrays can be
accesses either by simply using the array index after the dot, or by wrapping it in brackets.

```
.foo // true, property "foo" exists
.foo.bar // true, property "foo" is a dictionary containing "bar"
."foo"."bar" // true, same as above but quoted
.foo.x // false, "foo" doesn't contain "x"
."foo.bar" // false, property "foo.bar" doesn't exist
.foo.[0] // false, "foo" is not an array
.some.[0] // true, "some" is an array and has an element at index "0"
.some.0 // true, same as above but without brackets
.some."0" // false, "0" is a string key but "some" is not a dictionary
```

Comparisons can be expressed like this:

```
.value == "bar" // false
.value == 1 // true
.value != 1 // false
.value > 1 // false
.value >= 1 // true
.value < 1 // false
.value <= 1 // true
.string =~ /[Ss]ome/ // true
.string =~ /some/ // false
.string =~ /some/i // true, case insensitive
.string =~ "^Some" // true
```

Conditions (evaluated left to right):

```
.foo.bar == 1 and .value == 1 // true
.foo == 1 or .bar == 2 // false
.foo == 1 or .value == 1 // true
.foo not // true if foo does not exist
.foo and .bar not // true if foo and bar don't exist together
.foo or .bar not // true if neither foo nor bar exist
.foo.bar not // true if "bar" in dictionary "foo" doesn't exist
```

---
### mvt merge

Expand Down
4 changes: 3 additions & 1 deletion Sources/MVTCLI/Query.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ extension CLI {
var path: String

@Argument(help: "Search term, can be a string or a coordinate in the form 'latitude,longitude,tolerance(meters)'.")
var searchTerm: String
var searchTerms: [String]

mutating func run() async throws {
if let outputFile {
Expand All @@ -94,6 +94,8 @@ extension CLI {
}
}

let searchTerm = searchTerms.joined(separator: " ")

var coordinate: Coordinate3D?
var tolerance: CLLocationDistance?

Expand Down
7 changes: 7 additions & 0 deletions Sources/MVTTools/Extensions/DoubleExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

extension Float {

var asDouble: Double { Double(self) }

}
9 changes: 9 additions & 0 deletions Sources/MVTTools/Extensions/IntExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Foundation

extension BinaryInteger {

var asInt: Int { Int(self) }

var asUInt: UInt { UInt(self) }

}
26 changes: 26 additions & 0 deletions Sources/MVTTools/Extensions/StringExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Foundation

extension String {

var isNotEmpty: Bool { !isEmpty }

func matches(_ regex: String) -> Bool {
var options: String.CompareOptions = .regularExpression

var regex = regex
if regex.hasPrefix("/") {
regex.removeFirst()

if regex.hasSuffix("/i") {
options.insert(.caseInsensitive)
regex.removeLast(2)
}
else if regex.hasSuffix("/") {
regex.removeLast()
}
}

return self.range(of: regex, options: options) != nil
}

}
15 changes: 12 additions & 3 deletions Sources/MVTTools/Query.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,24 @@ extension VectorTile {
layerNames
}

let queryParser = QueryParser(string: term)

var result: [QueryResult] = []

for layerName in queryLayerNames {
guard let layerFeatureContainer = layers[layerName] else { continue }

let resultFeatures: [Feature] = layerFeatureContainer.features.filter({ feature in
for value in feature.properties.values.compactMap({ $0 as? String }) {
if value.contains(term) {
return true
if let queryParser,
let properties = feature.properties as? [String: AnyHashable]
{
return queryParser.evaluate(on: properties)
}
else {
for value in feature.properties.values.compactMap({ $0 as? String }) {
if value.contains(term) {
return true
}
}
}

Expand Down
Loading
Loading