This repository has been archived by the owner on Dec 4, 2017. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgl-style-schema.py
executable file
·142 lines (105 loc) · 3.69 KB
/
gl-style-schema.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#!/usr/bin/env python
import json
import re
import sys
import argparse
def extract_filter_fields(expr):
"""
Extract fields used in filter expressions
Resolve the combining filter expressions as well.
TODO: Does not work for all filter combinations yet
"""
if len(expr) < 1:
return
def is_combining_filter(f):
return f in ['all', 'any', 'none']
def is_existential_filter(f):
return f in ['has', '!has']
def is_comparison_filter(f):
return f in ["==", "!=", ">", ">=", "<", "<="]
def is_membership_filter(f):
return f in ['in', '!in']
fname = expr[0]
if (is_existential_filter(fname) or is_comparison_filter(fname) or
is_membership_filter(fname)):
yield expr[1]
elif is_combining_filter(fname):
for subexpr in expr[1:]:
yield subexpr[1]
def find_tokens(obj):
"Find {tokens} referencing a data property to pull from"
def extract_tokens(val):
match = re.search('{(\w*)}', val)
if match:
return match.groups()
else:
return []
for val in obj.values():
if isinstance(val, str) or isinstance(val, basestring):
for token in extract_tokens(val):
yield token
def is_special_key(key):
return key[0] == '$'
def extract_layout_fields(layer):
if 'layout' not in layer:
return
for field in find_tokens(layer['layout']):
yield field
def parse_style_layers(spec):
schema = VectorSchema()
for layer in spec['layers']:
if 'source-layer' in layer:
source = layer['source-layer']
for field in extract_filter_fields(layer.get('filter', [])):
schema.add_field(source, field)
for field in extract_layout_fields(layer):
schema.add_field(source, field)
return schema
def parse_tilejson_layers(spec):
schema = VectorSchema()
for layer in spec['vector_layers']:
source = layer['id']
for field in layer['fields'].keys():
schema.add_field(source, field)
return schema
class VectorSchema(object):
"Vector tile schema consisting out of several unique layers"
def __init__(self):
self.layers = {}
def add_field(self, layer_name, field_name):
"Record field usage into appropriate layer"
if layer_name not in self.layers:
self.layers[layer_name] = Layer(layer_name)
if not is_special_key(field_name):
self.layers[layer_name].add_field(field_name)
def __str__(self):
layers = sorted(self.layers.values(), key=lambda l: l.name)
return '\n'.join([l.__str__() for l in layers])
class Layer(object):
def __init__(self, name):
self.name = name
self.fields = set()
def add_field(self, field_name):
try:
self.fields.add(field_name)
# Ignore fields that are strings (false positives)
except TypeError:
pass
def __str__(self):
lines = ['#{0}'.format(self.name)]
for field_name in sorted(self.fields):
lines.append(' [{0}]'.format(field_name))
return '\n'.join(lines)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('specfile', nargs='?', type=argparse.FileType('r'),
default=sys.stdin)
args = parser.parse_args()
spec = json.loads(args.specfile.read())
if 'tilejson' in spec and 'vector_layers' in spec:
schema = parse_tilejson_layers(spec)
elif 'layers' in spec:
schema = parse_style_layers(spec)
else:
sys.exit('Input is neither a valid TileJSON spec or Mapbox GL style')
print(schema)