Skip to content

Commit

Permalink
Command line tool overhaul (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
trasch authored Aug 26, 2024
1 parent b6a77fb commit a6415a3
Show file tree
Hide file tree
Showing 19 changed files with 1,209 additions and 313 deletions.
6 changes: 3 additions & 3 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"originHash" : "5d734874e4d66aa5e42e2e73f545fbb1c3e33e7f08847495a19cf43690d8a3e5",
"originHash" : "e36efc60ff6513f9fd73eec6246018b16a077ae5221e7e54d785cf0a8c172a1a",
"pins" : [
{
"identity" : "gis-tools",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Outdooractive/gis-tools",
"state" : {
"revision" : "831a87ebd1ae3bb013c46b1d1752400dbc7bc352",
"version" : "1.7.0"
"revision" : "a8118bcd5e715a69f640476446fbf058fe39973f",
"version" : "1.8.3"
}
},
{
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ let package = Package(
name: "mvt-tools",
platforms: [
.iOS(.v13),
.macOS(.v10_15),
.macOS(.v13),
.tvOS(.v13),
.watchOS(.v6),
],
Expand All @@ -24,7 +24,7 @@ let package = Package(
targets: ["MVTTools"]),
],
dependencies: [
.package(url: "https://github.com/Outdooractive/gis-tools", from: "1.7.0"),
.package(url: "https://github.com/Outdooractive/gis-tools", from: "1.8.3"),
.package(url: "https://github.com/1024jp/GzipSwift.git", from: "5.2.0"),
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.4.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.1"),
Expand Down
57 changes: 40 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

# MVTTools

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 tool for working with vector tiles from the command line.

## Features

Expand All @@ -24,17 +24,17 @@ Mapbox vector tiles (MVT) reader/writer library for Swift, together with a tool
- Extract selected layers into a new tile
- Merge two tiles into one
- Can extract some infos from tiles like feature count, etc.
- Powerful command line tool (via [Homebrew](#command-line-tool), documentation below)
- Powerful command line tool (via [Homebrew](#command-line-tool), documentation below) for working with vector tiles and GeoJSON files

## Requirements

This package requires Swift 5.10 or higher (at least Xcode 14), and compiles on iOS (\>= iOS 13), macOS (\>= macOS 10.15), tvOS (\>= tvOS 13), watchOS (\>= watchOS 6) as well as Linux.
This package requires Swift 5.10 or higher (at least Xcode 14), and compiles on iOS (\>= iOS 13), macOS (\>= macOS 13), tvOS (\>= tvOS 13), watchOS (\>= watchOS 6) as well as Linux.

## Installation with Swift Package Manager

```swift
dependencies: [
.package(url: "https://github.com/Outdooractive/mvt-tools", from: "1.7.1"),
.package(url: "https://github.com/Outdooractive/mvt-tools", from: "1.8.0"),
],
targets: [
.target(name: "MyTarget", dependencies: [
Expand Down Expand Up @@ -105,11 +105,22 @@ You can install the command line tool `mvt` either
- with homebrew: `brew install Outdooractive/homebrew-tap/mvt-tools`
- or locally to `/usr/local/bin` with `./install_mvt.sh`

`mvt` works with vector tiles from local disk or served from a web server.
`mvt` works with vector tiles or GeoJSON files from local disk or served from a web server.

Layers in GeoJSON files (containing a FeatureCollection) can be represented by adding a property `vt_tile` to each Feature (can be overriden with a command line option).
`mvt` will add this property to all created GeoJSONs.

```bash
# mvt -h
OVERVIEW: A utility for inspecting and working with vector tiles.
OVERVIEW: A utility for inspecting and working with vector tiles (MVT) and GeoJSON files.

A x/y/z tile coordinate is needed for encoding/decoding vector tiles (MVT).
This tile coordinate can be extracted from the file path/URL if it's either in the form '/z/x/y' or 'z_x_y'.
Tile coordinates are not necessary for GeoJSON input files.
Examples:
- Tests/MVTToolsTests/TestData/14_8716_8015.vector.mvt
- https://demotiles.maplibre.org/tiles/2/2/1.pbf
USAGE: mvt <subcommand>
Expand All @@ -118,19 +129,19 @@ OPTIONS:
-h, --help Show help information.
SUBCOMMANDS:
dump (default) Print the vector tile as GeoJSON
info Print information about the vector tile
merge Merge two or more vector tiles
query Query the features in a vector tile
export Export the vector tile as GeoJSON
import Import some GeoJSONs to a vector tile
dump (default) Print the input file (mvt or GeoJSON) as pretty-printed GeoJSON to the console
info Print information about the input file (mvt or GeoJSON)
query Query the features in the input file (mvt or GeoJSON)
merge Merge any number of vector tiles or GeoJSONs
import Import some GeoJSONs into a vector tile
export Export a vector tile as GeoJSON to a file
See 'mvt help <subcommand>' for detailed help.
```
---
### mvt dump
Print a vector tile as GeoJSON.
Print a vector tile or GeoJSON file as pretty-printed GeoJSON.
```bash
mvt dump Tests/MVTToolsTests/TestData/14_8716_8015.vector.mvt
Expand Down Expand Up @@ -164,6 +175,8 @@ mvt dump Tests/MVTToolsTests/TestData/14_8716_8015.vector.mvt
---
### mvt info
Print some informations about vector tiles/GeoJSONs.
**Example 1**: Print information about the MVTTools test vector tile at zoom 14, at Yaoundé, Cameroon.
```bash
Expand Down Expand Up @@ -196,7 +209,7 @@ mvt info https://demotiles.maplibre.org/tiles/2/2/1.pbf
---
### mvt query
Query a vector tile with a search term.
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 @@ -235,7 +248,7 @@ mvt query Tests/MVTToolsTests/TestData/14_8716_8015.vector.mvt "École"
Query a tile with `latitude,longitude,radius`.
```bash
mvt query Tests/MVTToolsTests/TestData/14_8716_8015.vector.mvt "3.87324,11.53731,1000"
mvt query Tests/MVTToolsTests/TestData/14_8716_8015.geojson "3.87324,11.53731,1000"
{
"features" : [
{
Expand Down Expand Up @@ -265,10 +278,20 @@ mvt query Tests/MVTToolsTests/TestData/14_8716_8015.vector.mvt "3.87324,11.53731
---
### mvt merge
Merge two or more vector tiles.
Merge two or more vector tiles or GeoJSON files in any combination.
```bash
# All vector tiles:
mvt merge --output merged.mvt path/to/first.mvt path/to/second.mvt
# All GeoJSON files:
mvt merge --output merged.geojson path/to/first.geojson path/to/second.geojson
# Merge GeoJSON files into a vector tile:
mvt merge --output merged.mvt --output-format mvt path/to/first.geojson path/to/second.geojson
# Merge vector tiles into a GeoJSOn file:
mvt merge --output merged.geojson --output-format geojson path/to/first.mvt path/to/second.mvt
```
---
### mvt export
Expand Down Expand Up @@ -335,6 +358,6 @@ brew install protobuf swift-protobuf swiftlint
MIT
# Author
# Authors
Thomas Rasch, Outdooractive
122 changes: 76 additions & 46 deletions Sources/MVTCLI/CLI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,29 @@ import MVTTools
@main
struct CLI: AsyncParsableCommand {

static let logger = Logger(label: "mvttool")
static let logger = Logger(label: "mvt")

static let configuration = CommandConfiguration(
commandName: "mvt",
abstract: "A utility for inspecting and working with vector tiles.",
abstract: "A utility for inspecting and working with vector tiles (MVT) and GeoJSON files.",
discussion: """
A x/y/z tile coordinate is needed for encoding/decoding vector tiles (MVT).
This tile coordinate can be extracted from the path/URL if it's either in the form '/z/x/y' or 'z_x_y'.
Tile coordinates are not necessary for GeoJSON files.
Examples:
- Tests/MVTToolsTests/TestData/14_8716_8015.vector.mvt
- https://demotiles.maplibre.org/tiles/2/2/1.pbf
""",
version: cliVersion,
subcommands: [Dump.self, Info.self, Merge.self, Query.self, Export.self, Import.self],
subcommands: [
Dump.self,
Info.self,
Query.self,
Merge.self,
Import.self,
Export.self,
],
defaultSubcommand: Dump.self)

}
Expand All @@ -25,53 +41,35 @@ struct CLIError: LocalizedError {
}
}

struct Options: ParsableArguments {

@Flag(name: .shortAndLong, help: "Print some debug info")
var verbose = false
struct XYZOptions: ParsableArguments {

@Option(name: .short, help: "Tile zoom level - if it can't be extracted from the path")
var z: Int?

@Option(name: .short, help: "Tile x coordinate - if it can't be extracted from the path")
@Option(
name: .short,
help: "Tile x coordinate, if it can't be extracted from the path.")
var x: Int?

@Option(name: .short, help: "Tile y coordinate - if it can't be extracted from the path")
@Option(
name: .short,
help: "Tile y coordinate, if it can't be extracted from the path.")
var y: Int?

@Argument(
help: "The MVT resource (file or URL). The tile coordinate can be extracted from the path if it's either in the form '/z/x/y' or 'z_x_y'",
completion: .file(extensions: ["pbf", "mvt"]))
var path: String
@Option(
name: .short,
help: "Tile zoom level, if it can't be extracted from the path.")
var z: Int?

// Try to parse x/y/z from the path/URL
mutating func parseUrl(
extractCoordinate: Bool = true,
checkExistence: Bool = true)
throws -> URL
/// Try to extract x, y and z tile coordinates from some file paths or URLs,
/// if the were not given on the command line
mutating func parseXYZ(
fromPaths paths: [String])
throws -> (x: Int, y: Int, z: Int)
{
let url: URL
if path.hasPrefix("http") {
guard let parsedUrl = URL(string: path) else {
throw CLIError("\(path) is not a valid URL")
}
url = parsedUrl
}
else {
url = URL(fileURLWithPath: path)
if checkExistence {
guard try url.checkResourceIsReachable() else {
throw CLIError("The file '\(path)' doesn't exist.")
}
}
}
for path in paths {
guard x == nil
|| y == nil
|| z == nil
else { break }

guard extractCoordinate else { return url }

if x == nil
|| y == nil
|| z == nil
{
let urlParts = path.extractingGroupsUsingPattern("\\/(\\d+)\\/(\\d+)\\/(\\d+)(?:\\/|\\.)", caseInsensitive: false)
if urlParts.count >= 3 {
if let partX = Int(urlParts[1]),
Expand All @@ -98,10 +96,9 @@ struct Options: ParsableArguments {
}
}

guard let x,
let y,
let z
else { throw CLIError("Need z, x and y") }
guard let x, let y, let z else {
throw CLIError("Need z, x and y")
}

guard x >= 0 else { throw CLIError("x must be >= 0") }
guard y >= 0 else { throw CLIError("y must be >= 0") }
Expand All @@ -111,6 +108,39 @@ struct Options: ParsableArguments {
if x >= maximumTileCoordinate { throw CLIError("x at zoom \(z) must be smaller than \(maximumTileCoordinate)") }
if y >= maximumTileCoordinate { throw CLIError("y at zoom \(z) must be smaller than \(maximumTileCoordinate)") }

return (x, y, z)
}

}

struct Options: ParsableArguments {

@Flag(
name: .shortAndLong,
help: "Print some debug info.")
var verbose = false

func parseUrl(
fromPath path: String,
checkPathExistence: Bool = true)
throws -> URL
{
let url: URL
if path.hasPrefix("http") {
guard let parsedUrl = URL(string: path) else {
throw CLIError("\(path) is not a valid URL")
}
url = parsedUrl
}
else {
url = URL(fileURLWithPath: path)
if checkPathExistence {
guard try url.checkResourceIsReachable() else {
throw CLIError("The file '\(path)' doesn't exist.")
}
}
}

return url
}

Expand Down
Loading

0 comments on commit a6415a3

Please sign in to comment.