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

mvt info can now count property values #31

Merged
merged 2 commits into from
Sep 2, 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
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
Loading