Skip to content

Commit

Permalink
mvt info can now count property values (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
trasch authored Sep 2, 2024
1 parent 207cb09 commit 6c2f59e
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 40 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,6 @@ brew install protobuf swift-protobuf swiftlint
- Documentation (!)
- Tests
- Decode V1 tiles
- Locking (when updating/deleting features, indexing)
- Query option: within/intersects
Expand Down
12 changes: 3 additions & 9 deletions Sources/MVTCLI/Extensions/ArrayExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import Foundation

extension Array {

var nonempty: Self? {
isEmpty ? nil : self
}
var nonempty: Self? { isEmpty ? nil : self }

var isNotEmpty: Bool { !isEmpty }

Expand All @@ -25,13 +23,9 @@ extension Array {

extension Array where Element: Hashable {

var asSet: Set<Element> {
Set(self)
}
var asSet: Set<Element> { Set(self) }

var uniqued: Self {
Array(Set(self))
}
var uniqued: Self { Array(Set(self)) }

}

Expand Down
4 changes: 1 addition & 3 deletions Sources/MVTCLI/Extensions/IntExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import Foundation

extension Int {

var toString: String {
String(self)
}
var toString: String { String(self) }

}
4 changes: 1 addition & 3 deletions Sources/MVTCLI/Extensions/OptionalProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ public protocol OptionalProtocol {

extension Optional: OptionalProtocol {

public var optional: Wrapped? {
self
}
public var optional: Wrapped? { self }

}
6 changes: 3 additions & 3 deletions Sources/MVTCLI/Extensions/SetExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import Foundation

extension Set {

var asArray: [Element] {
Array(self)
}
var isNotEmpty: Bool { !isEmpty }

var asArray: [Element] { Array(self) }

}
107 changes: 100 additions & 7 deletions Sources/MVTCLI/Info.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ extension CLI {

struct Info: AsyncParsableCommand {

enum InfoTables: String, CaseIterable {
case features
case properties
}

static let configuration = CommandConfiguration(
abstract: "Print information about the input file (MVT or GeoJSON)",
discussion: """
Expand All @@ -19,14 +14,22 @@ extension CLI {
in the input file.
- properties: Counts of all Feature properties for each layer in the
input file.
- property=<property>: Count the values for '<property>' across all layers
and features (can be repeated). Note: This doesn't
work for Array and Dictionary values.
""")

@Option(
name: .shortAndLong,
help: "The tables to print, comma separated list of '\(InfoTables.allCases.map(\.rawValue).joined(separator: ","))'.",
transform: { $0.components(separatedBy: ",").compactMap(InfoTables.init(rawValue:)) })
transform: InfoTables.parse)
var infoTables: [InfoTables] = [.features, .properties]

@Option(
name: .shortAndLong,
help: "Shortcut for -i property=<property> (can be repeated).")
var property: [String] = []

@OptionGroup
var options: Options

Expand All @@ -39,13 +42,17 @@ extension CLI {
let url = try options.parseUrl(fromPath: path)

guard var layers = VectorTile.tileInfo(at: url)
?? VectorTile(contentsOfGeoJson: url)?.tileInfo()
?? VectorTile(contentsOfGeoJson: url, layerProperty: nil)?.tileInfo()
else { throw CLIError("Error retreiving the tile info for '\(path)'") }

if options.verbose {
print("Info for tile '\(url.lastPathComponent)'")
}

if property.isNotEmpty {
infoTables = [.property(property.uniqued)]
}

layers.sort { first, second in
first.name.compare(second.name) == .orderedAscending
}
Expand All @@ -56,6 +63,8 @@ extension CLI {
dumpFeatures(layers)
case .properties:
dumpProperties(layers)
case let .property(names):
dumpProperty(layers, names: names)
}

if index < infoTables.count - 1 {
Expand Down Expand Up @@ -116,6 +125,39 @@ extension CLI {
print(result)
}

func dumpProperty(_ layers: [VectorTile.LayerInfo], names: [String]) {
let propertyValues: [String: [String: Int]] = layers.reduce(into: [:]) { result, layer in
for name in names {
guard let values = layer.propertyValues[name] else { continue }

var thisNameValues = result[name] ?? [:]
thisNameValues.merge(values) { $0 + $1 }
result[name] = thisNameValues
}
}
let propertyNames = propertyValues.flatMap({ $1.keys }).sorted()

var tableHeader = ["Name"]
tableHeader.append(contentsOf: propertyNames)

var table: [[String]] = []
table.append(names)

for propertyName in propertyNames {
var column: [String] = []
for name in names {
column.append((propertyValues[name]?[propertyName] ?? 0).toString)
}
table.append(column)
}

let result = dumpSideBySide(
table,
asTableWithHeaders: tableHeader)

print(result)
}

private func dumpSideBySide(
_ strings: [[String]],
asTableWithHeaders headers: [String])
Expand Down Expand Up @@ -173,4 +215,55 @@ extension CLI {

}

// MARK: - InfoTables

enum InfoTables: CaseIterable {
case features
case properties
case property([String])

static var allCases: [InfoTables] {
[.features, .properties, .property([])]
}

@Sendable
static func parse(_ rawValue: String) -> [InfoTables] {
var result: [InfoTables] = []
var properties: Set<String> = []

let components = rawValue.components(separatedBy: ",")
for component in components {
if component == "features" {
result.append(.features)
continue
}
if component == "properties" {
result.append(.properties)
continue
}

let propertyParts = component.components(separatedBy: "=")
guard propertyParts.count == 2,
propertyParts[0] == "property"
else { continue }

properties.insert(propertyParts[1])
}

if properties.isNotEmpty {
result.append(.property(properties.sorted()))
}

return result
}

var rawValue: String {
switch self {
case .features: "features"
case .properties: "properties"
case .property: "property"
}
}
}

}
36 changes: 22 additions & 14 deletions Sources/MVTTools/Info.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ extension VectorTile {
public let polygonFeatures: Int
public let unknownFeatures: Int
public let propertyNames: [String: Int]
public let propertyValues: [String: [String: Int]]
public let version: Int?
}

Expand All @@ -31,23 +32,13 @@ extension VectorTile {
return layerNames(from: data)
}

// [{
// name: 'world',
// features: 1,
// point_features: 0,
// linestring_features: 0,
// polygon_features: 1,
// unknown_features: 0,
// property_names: [:],
// version: 2
// }]

/// Information about the features in a tile, per layer.
public func tileInfo() -> [LayerInfo]? {
var result: [LayerInfo] = []

for (layerName, layerContainer) in layers {
var propertyNames: [String: Int] = [:]
var propertyValues: [String: [String: Int]] = [:]

var pointFeatures = 0
var lineStringFeatures = 0
Expand All @@ -62,8 +53,14 @@ extension VectorTile {
default: unknownFeatures += 1
}

for key in feature.properties.keys {
for (key, value) in feature.properties {
propertyNames[key, default: 0] += 1

if let value = value as? CustomStringConvertible {
var thisKeyValues = propertyValues[key] ?? [:]
thisKeyValues[value.description, default: 0] += 1
propertyValues[key] = thisKeyValues
}
}
}

Expand All @@ -75,6 +72,7 @@ extension VectorTile {
polygonFeatures: polygonFeatures,
unknownFeatures: unknownFeatures,
propertyNames: propertyNames,
propertyValues: propertyValues,
version: nil))
}

Expand All @@ -88,8 +86,9 @@ extension VectorTile {
var result: [LayerInfo] = []

for layer in tile.layers {
let keys: [String] = layer.keys
let (keys, values) = MVTDecoder.keysAndValues(forLayer: layer)
var propertyNames: [String: Int] = [:]
var propertyValues: [String: [String: Int]] = [:]

var pointFeatures = 0
var lineStringFeatures = 0
Expand All @@ -105,9 +104,17 @@ extension VectorTile {
}

for tags in feature.tags.pairs() {
guard let key: String = keys.get(at: Int(tags.first)) else { continue }
guard let key: String = keys.get(at: Int(tags.first)),
let value: Sendable = values.get(at: Int(tags.second))
else { continue }

propertyNames[key, default: 0] += 1

if let value = value as? CustomStringConvertible {
var thisKeyValues = propertyValues[key] ?? [:]
thisKeyValues[value.description, default: 0] += 1
propertyValues[key] = thisKeyValues
}
}
}

Expand All @@ -119,6 +126,7 @@ extension VectorTile {
polygonFeatures: polygonFeatures,
unknownFeatures: unknownFeatures,
propertyNames: propertyNames,
propertyValues: propertyValues,
version: Int(layer.version)))
}

Expand Down

0 comments on commit 6c2f59e

Please sign in to comment.