pycartosym 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycartosym-0.2.0/LICENSE +21 -0
- pycartosym-0.2.0/PKG-INFO +180 -0
- pycartosym-0.2.0/README.md +115 -0
- pycartosym-0.2.0/pyproject.toml +129 -0
- pycartosym-0.2.0/setup.cfg +4 -0
- pycartosym-0.2.0/src/pycartosym/__init__.py +38 -0
- pycartosym-0.2.0/src/pycartosym/ast.py +160 -0
- pycartosym-0.2.0/src/pycartosym/ast_converter.py +1307 -0
- pycartosym-0.2.0/src/pycartosym/cli.py +485 -0
- pycartosym-0.2.0/src/pycartosym/cli_style.py +138 -0
- pycartosym-0.2.0/src/pycartosym/codecs/__init__.py +96 -0
- pycartosym-0.2.0/src/pycartosym/codecs/_cascade.py +165 -0
- pycartosym-0.2.0/src/pycartosym/codecs/base.py +105 -0
- pycartosym-0.2.0/src/pycartosym/codecs/cscss/__init__.py +16 -0
- pycartosym-0.2.0/src/pycartosym/codecs/cscss/reader.py +43 -0
- pycartosym-0.2.0/src/pycartosym/codecs/cscss/writer.py +22 -0
- pycartosym-0.2.0/src/pycartosym/codecs/csjson/__init__.py +16 -0
- pycartosym-0.2.0/src/pycartosym/codecs/csjson/reader.py +31 -0
- pycartosym-0.2.0/src/pycartosym/codecs/csjson/writer.py +40 -0
- pycartosym-0.2.0/src/pycartosym/codecs/maplibre/__init__.py +16 -0
- pycartosym-0.2.0/src/pycartosym/codecs/maplibre/_expressions.py +396 -0
- pycartosym-0.2.0/src/pycartosym/codecs/maplibre/_filter.py +374 -0
- pycartosym-0.2.0/src/pycartosym/codecs/maplibre/_layers.py +808 -0
- pycartosym-0.2.0/src/pycartosym/codecs/maplibre/_raster.py +164 -0
- pycartosym-0.2.0/src/pycartosym/codecs/maplibre/_zoom.py +284 -0
- pycartosym-0.2.0/src/pycartosym/codecs/maplibre/reader.py +76 -0
- pycartosym-0.2.0/src/pycartosym/codecs/maplibre/writer.py +1305 -0
- pycartosym-0.2.0/src/pycartosym/codecs/sld/__init__.py +60 -0
- pycartosym-0.2.0/src/pycartosym/codecs/sld/_dialect.py +247 -0
- pycartosym-0.2.0/src/pycartosym/codecs/sld/_filter.py +521 -0
- pycartosym-0.2.0/src/pycartosym/codecs/sld/_symbolizer.py +2318 -0
- pycartosym-0.2.0/src/pycartosym/codecs/sld/_types.py +400 -0
- pycartosym-0.2.0/src/pycartosym/codecs/sld/_xml_helpers.py +83 -0
- pycartosym-0.2.0/src/pycartosym/codecs/sld/reader.py +241 -0
- pycartosym-0.2.0/src/pycartosym/codecs/sld/writer.py +294 -0
- pycartosym-0.2.0/src/pycartosym/codecs/sld_se/__init__.py +14 -0
- pycartosym-0.2.0/src/pycartosym/codecs/sld_se/reader.py +11 -0
- pycartosym-0.2.0/src/pycartosym/codecs/sld_se/writer.py +11 -0
- pycartosym-0.2.0/src/pycartosym/converter.py +1172 -0
- pycartosym-0.2.0/src/pycartosym/cql2/__init__.py +56 -0
- pycartosym-0.2.0/src/pycartosym/cql2/_expr_alignment.py +44 -0
- pycartosym-0.2.0/src/pycartosym/cql2/_expr_arithmetic.py +62 -0
- pycartosym-0.2.0/src/pycartosym/cql2/_expr_array.py +28 -0
- pycartosym-0.2.0/src/pycartosym/cql2/_expr_base.py +92 -0
- pycartosym-0.2.0/src/pycartosym/cql2/_expr_boolean.py +40 -0
- pycartosym-0.2.0/src/pycartosym/cql2/_expr_character.py +81 -0
- pycartosym-0.2.0/src/pycartosym/cql2/_expr_color.py +54 -0
- pycartosym-0.2.0/src/pycartosym/cql2/_expr_comparison.py +55 -0
- pycartosym-0.2.0/src/pycartosym/cql2/_expr_generic.py +133 -0
- pycartosym-0.2.0/src/pycartosym/cql2/_expr_polymorphic.py +165 -0
- pycartosym-0.2.0/src/pycartosym/cql2/_expr_spatial.py +212 -0
- pycartosym-0.2.0/src/pycartosym/cql2/_expr_temporal.py +187 -0
- pycartosym-0.2.0/src/pycartosym/cql2/_expr_value.py +60 -0
- pycartosym-0.2.0/src/pycartosym/cql2/from_cql2text.py +584 -0
- pycartosym-0.2.0/src/pycartosym/cql2/from_text.py +1411 -0
- pycartosym-0.2.0/src/pycartosym/cql2/model.py +245 -0
- pycartosym-0.2.0/src/pycartosym/cql2/to_json.py +697 -0
- pycartosym-0.2.0/src/pycartosym/cql2/to_text.py +295 -0
- pycartosym-0.2.0/src/pycartosym/cql2/vocab.py +139 -0
- pycartosym-0.2.0/src/pycartosym/exceptions.py +40 -0
- pycartosym-0.2.0/src/pycartosym/grammar/generated/CartoSymCSSGrammar.py +3149 -0
- pycartosym-0.2.0/src/pycartosym/grammar/generated/CartoSymCSSGrammarListener.py +453 -0
- pycartosym-0.2.0/src/pycartosym/grammar/generated/CartoSymCSSLexer.py +245 -0
- pycartosym-0.2.0/src/pycartosym/grammar/generated/__init__.py +5 -0
- pycartosym-0.2.0/src/pycartosym/grammar/generated/cql2text/CQL2TextLexer.py +682 -0
- pycartosym-0.2.0/src/pycartosym/grammar/generated/cql2text/CQL2TextListener.py +516 -0
- pycartosym-0.2.0/src/pycartosym/grammar/generated/cql2text/CQL2TextParser.py +4153 -0
- pycartosym-0.2.0/src/pycartosym/grammar/generated/cql2text/__init__.py +5 -0
- pycartosym-0.2.0/src/pycartosym/models/__init__.py +100 -0
- pycartosym-0.2.0/src/pycartosym/models/base.py +64 -0
- pycartosym-0.2.0/src/pycartosym/models/de9im.py +210 -0
- pycartosym-0.2.0/src/pycartosym/models/styles.py +100 -0
- pycartosym-0.2.0/src/pycartosym/models/symbolizers.py +687 -0
- pycartosym-0.2.0/src/pycartosym/models/types.py +342 -0
- pycartosym-0.2.0/src/pycartosym/models/value_expressions.py +212 -0
- pycartosym-0.2.0/src/pycartosym/parser.py +1320 -0
- pycartosym-0.2.0/src/pycartosym/schemas/CartoSym-JSON.schema.json +1873 -0
- pycartosym-0.2.0/src/pycartosym.egg-info/PKG-INFO +180 -0
- pycartosym-0.2.0/src/pycartosym.egg-info/SOURCES.txt +117 -0
- pycartosym-0.2.0/src/pycartosym.egg-info/dependency_links.txt +1 -0
- pycartosym-0.2.0/src/pycartosym.egg-info/entry_points.txt +2 -0
- pycartosym-0.2.0/src/pycartosym.egg-info/requires.txt +23 -0
- pycartosym-0.2.0/src/pycartosym.egg-info/top_level.txt +1 -0
- pycartosym-0.2.0/tests/test_alter.py +215 -0
- pycartosym-0.2.0/tests/test_cascade.py +137 -0
- pycartosym-0.2.0/tests/test_cli.py +225 -0
- pycartosym-0.2.0/tests/test_convert_literal_value.py +51 -0
- pycartosym-0.2.0/tests/test_cql2_api.py +37 -0
- pycartosym-0.2.0/tests/test_cql2_expressions.py +266 -0
- pycartosym-0.2.0/tests/test_cql2_extended.py +476 -0
- pycartosym-0.2.0/tests/test_cql2_parser.py +464 -0
- pycartosym-0.2.0/tests/test_cql2_vocab.py +66 -0
- pycartosym-0.2.0/tests/test_cql2text_grammar_corpus.py +83 -0
- pycartosym-0.2.0/tests/test_cql2text_treewalker.py +435 -0
- pycartosym-0.2.0/tests/test_csjson_strictness.py +78 -0
- pycartosym-0.2.0/tests/test_de9im.py +229 -0
- pycartosym-0.2.0/tests/test_expression_parser_robustness.py +101 -0
- pycartosym-0.2.0/tests/test_expression_parser_tree.py +249 -0
- pycartosym-0.2.0/tests/test_geoserver_sld_corpus.py +46 -0
- pycartosym-0.2.0/tests/test_include.py +193 -0
- pycartosym-0.2.0/tests/test_maplibre_corpus.py +71 -0
- pycartosym-0.2.0/tests/test_maplibre_expressions.py +305 -0
- pycartosym-0.2.0/tests/test_maplibre_filter.py +345 -0
- pycartosym-0.2.0/tests/test_maplibre_reader.py +1015 -0
- pycartosym-0.2.0/tests/test_maplibre_writer.py +2452 -0
- pycartosym-0.2.0/tests/test_maplibre_zoom.py +152 -0
- pycartosym-0.2.0/tests/test_marker_antlr.py +245 -0
- pycartosym-0.2.0/tests/test_roundtrip.py +1367 -0
- pycartosym-0.2.0/tests/test_sld10_corpus.py +70 -0
- pycartosym-0.2.0/tests/test_sld_cascade.py +110 -0
- pycartosym-0.2.0/tests/test_sld_filter_spatial.py +63 -0
- pycartosym-0.2.0/tests/test_sld_geoserver_corpus.py +109 -0
- pycartosym-0.2.0/tests/test_sld_reader.py +786 -0
- pycartosym-0.2.0/tests/test_sld_roundtrip.py +234 -0
- pycartosym-0.2.0/tests/test_sld_se_geostyler.py +64 -0
- pycartosym-0.2.0/tests/test_sld_writer.py +2130 -0
- pycartosym-0.2.0/tests/test_sld_xsd.py +62 -0
- pycartosym-0.2.0/tests/test_symbolizer_vendor_extension.py +63 -0
- pycartosym-0.2.0/tests/test_variable_and_stylingrulename.py +171 -0
pycartosym-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Maxime Collombin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pycartosym
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Lossless transcoding between CartoSym CSS and other encodings for OGC Style & Symbology
|
|
5
|
+
Author-email: Maxime Collombin <maxime.collombin@netplus.ch>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Maxime Collombin
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/maxcollombin/pycartosym
|
|
29
|
+
Project-URL: Documentation, https://github.com/maxcollombin/pycartosym/blob/main/README.md
|
|
30
|
+
Project-URL: Repository, https://github.com/maxcollombin/pycartosym.git
|
|
31
|
+
Project-URL: Issues, https://github.com/maxcollombin/pycartosym/issues
|
|
32
|
+
Keywords: gis,cartography,symbology,ogc,sld,cartosym
|
|
33
|
+
Classifier: Development Status :: 3 - Alpha
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
42
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
43
|
+
Requires-Python: >=3.10
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
License-File: LICENSE
|
|
46
|
+
Requires-Dist: antlr4-python3-runtime>=4.13.0
|
|
47
|
+
Requires-Dist: pydantic>=2.0.0
|
|
48
|
+
Requires-Dist: jsonschema>=4.0.0
|
|
49
|
+
Provides-Extra: dev
|
|
50
|
+
Requires-Dist: antlr4-tools>=0.2.0; extra == "dev"
|
|
51
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
52
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
53
|
+
Requires-Dist: black; extra == "dev"
|
|
54
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
55
|
+
Requires-Dist: mypy; extra == "dev"
|
|
56
|
+
Requires-Dist: xmlschema>=3.0; extra == "dev"
|
|
57
|
+
Provides-Extra: sld
|
|
58
|
+
Requires-Dist: lxml>=4.9.0; extra == "sld"
|
|
59
|
+
Provides-Extra: maplibre
|
|
60
|
+
Provides-Extra: cql2
|
|
61
|
+
Requires-Dist: pygeofilter>=0.2.0; extra == "cql2"
|
|
62
|
+
Provides-Extra: all
|
|
63
|
+
Requires-Dist: pycartosym[cql2,dev,maplibre,sld]; extra == "all"
|
|
64
|
+
Dynamic: license-file
|
|
65
|
+
|
|
66
|
+
# pycartosym
|
|
67
|
+
|
|
68
|
+
[](https://github.com/maxcollombin/pycartosym/actions/workflows/ci.yml)
|
|
69
|
+
|
|
70
|
+
A Python package for lossless transcoding between [CartoSym CSS](https://github.com/opengeospatial/styles-and-symbology) (`.cscss`) and other encodings of the OGC Style & Symbology conceptual model (CS-JSON, SLD/SE), plus MapLibre Style as a practical interoperability target.
|
|
71
|
+
|
|
72
|
+
## Supported Formats
|
|
73
|
+
|
|
74
|
+
CartoSym-CSS (`.cscss`) and CS-JSON (`.cs.json`) are the two OGC Style &
|
|
75
|
+
Symbology encodings this package reads and writes losslessly. SLD/SE
|
|
76
|
+
(`.sld`, 1.1.0 and 1.0.0, including SLD 1.0.0 with GeoServer
|
|
77
|
+
`<VendorOption>` pass-through) is also OGC. MapLibre Style (`.json`) is
|
|
78
|
+
the MapLibre/Mapbox de facto spec, not OGC, supported as a practical
|
|
79
|
+
interoperability target. All are read and written.
|
|
80
|
+
|
|
81
|
+
Each codec is covered for the constructs the target format actually
|
|
82
|
+
expresses (a symbolizer or property with no equivalent on the other side
|
|
83
|
+
raises rather than silently dropping data).
|
|
84
|
+
|
|
85
|
+
## Installation
|
|
86
|
+
|
|
87
|
+
### From PyPI (Coming Soon)
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pip install pycartosym
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### From Source
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
git clone https://github.com/maxcollombin/pycartosym.git
|
|
97
|
+
cd pycartosym
|
|
98
|
+
uv sync --all-extras
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The generated ANTLR lexer/parser are already committed under
|
|
102
|
+
`src/pycartosym/grammar/generated/`, so no separate grammar build
|
|
103
|
+
step is needed. Prefix commands with `uv run`, or activate the
|
|
104
|
+
project's `.venv` with `source .venv/bin/activate`.
|
|
105
|
+
|
|
106
|
+
## Usage
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Format auto-detected from the file extensions where that's unambiguous
|
|
110
|
+
cartosym <input-file> -o <output-file>
|
|
111
|
+
|
|
112
|
+
# Explicit target format (needed for the SLD dialects and MapLibre,
|
|
113
|
+
# since a plain .sld/.json extension can't disambiguate those)
|
|
114
|
+
cartosym --to-format <sld|sld:1.0.0|sld:geoserver|maplibre|cscss|csjson> <input-file> -o <output-file>
|
|
115
|
+
|
|
116
|
+
# Display structure info for a CSCSS file, without converting it
|
|
117
|
+
cartosym parse <input-file>
|
|
118
|
+
|
|
119
|
+
# Validate a .cscss or .cs.json file against the grammar/JSON schema
|
|
120
|
+
cartosym validate <input-file>
|
|
121
|
+
|
|
122
|
+
# Full option and format reference
|
|
123
|
+
cartosym --help
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
A few concrete conversions, using the files under [`examples/`](examples/)
|
|
127
|
+
(`examples/sld/` for SLD/SE):
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
cartosym examples/0-basic.cscss -o out.cs.json
|
|
131
|
+
cartosym examples/13-vector-point-circle.cscss --to-format sld -o out.sld
|
|
132
|
+
cartosym examples/sld/1-polygon-fill-stroke.sld --to-format maplibre -o out.json
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Python API
|
|
136
|
+
|
|
137
|
+
Most users work through the `cartosym` CLI above. The `cql2` submodule also
|
|
138
|
+
exposes CQL2 (the OGC 21-065r2 query-expression sublanguage that
|
|
139
|
+
CartoSym-CSS embeds in `[ filter ]` selectors and CS-JSON encodes as
|
|
140
|
+
`{"op": …, "args": …}`) as a stable, importable API:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from pycartosym import cql2
|
|
144
|
+
|
|
145
|
+
# CQL2-Text -> expression model
|
|
146
|
+
expr = cql2.parse_text("population > 1000")
|
|
147
|
+
|
|
148
|
+
# Expression model -> CQL2-JSON
|
|
149
|
+
cql2.to_cql2_json(expr)
|
|
150
|
+
# {'op': '>', 'args': [{'property': 'population'}, 1000]}
|
|
151
|
+
|
|
152
|
+
# CQL2-JSON selector dict -> CartoSym-CSS filter text
|
|
153
|
+
cql2.to_cql2_text({
|
|
154
|
+
"op": "and",
|
|
155
|
+
"args": [
|
|
156
|
+
{"op": "=", "args": [{"property": "a"}, 1]},
|
|
157
|
+
{"op": "like", "args": [{"property": "b"}, "x%"]},
|
|
158
|
+
],
|
|
159
|
+
})
|
|
160
|
+
# "a = 1 and b like 'x%'"
|
|
161
|
+
|
|
162
|
+
# Operator/function vocabulary, derived from the CQL2 Pydantic models
|
|
163
|
+
cql2.vocab.SPATIAL_RELATE # 's_relate'
|
|
164
|
+
"s_intersects" in cql2.vocab.KNOWN_CQL2_CALLS # True
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`cql2.parse_tree` is a lower-level entry point for converting an
|
|
168
|
+
already-parsed ANTLR `expression` context (as produced while walking the
|
|
169
|
+
CartoSym-CSS grammar) — most callers want `parse_text`.
|
|
170
|
+
|
|
171
|
+
## Development
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
uv sync --all-extras
|
|
175
|
+
uv run pytest
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# pycartosym
|
|
2
|
+
|
|
3
|
+
[](https://github.com/maxcollombin/pycartosym/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
A Python package for lossless transcoding between [CartoSym CSS](https://github.com/opengeospatial/styles-and-symbology) (`.cscss`) and other encodings of the OGC Style & Symbology conceptual model (CS-JSON, SLD/SE), plus MapLibre Style as a practical interoperability target.
|
|
6
|
+
|
|
7
|
+
## Supported Formats
|
|
8
|
+
|
|
9
|
+
CartoSym-CSS (`.cscss`) and CS-JSON (`.cs.json`) are the two OGC Style &
|
|
10
|
+
Symbology encodings this package reads and writes losslessly. SLD/SE
|
|
11
|
+
(`.sld`, 1.1.0 and 1.0.0, including SLD 1.0.0 with GeoServer
|
|
12
|
+
`<VendorOption>` pass-through) is also OGC. MapLibre Style (`.json`) is
|
|
13
|
+
the MapLibre/Mapbox de facto spec, not OGC, supported as a practical
|
|
14
|
+
interoperability target. All are read and written.
|
|
15
|
+
|
|
16
|
+
Each codec is covered for the constructs the target format actually
|
|
17
|
+
expresses (a symbolizer or property with no equivalent on the other side
|
|
18
|
+
raises rather than silently dropping data).
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
### From PyPI (Coming Soon)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install pycartosym
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### From Source
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git clone https://github.com/maxcollombin/pycartosym.git
|
|
32
|
+
cd pycartosym
|
|
33
|
+
uv sync --all-extras
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The generated ANTLR lexer/parser are already committed under
|
|
37
|
+
`src/pycartosym/grammar/generated/`, so no separate grammar build
|
|
38
|
+
step is needed. Prefix commands with `uv run`, or activate the
|
|
39
|
+
project's `.venv` with `source .venv/bin/activate`.
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Format auto-detected from the file extensions where that's unambiguous
|
|
45
|
+
cartosym <input-file> -o <output-file>
|
|
46
|
+
|
|
47
|
+
# Explicit target format (needed for the SLD dialects and MapLibre,
|
|
48
|
+
# since a plain .sld/.json extension can't disambiguate those)
|
|
49
|
+
cartosym --to-format <sld|sld:1.0.0|sld:geoserver|maplibre|cscss|csjson> <input-file> -o <output-file>
|
|
50
|
+
|
|
51
|
+
# Display structure info for a CSCSS file, without converting it
|
|
52
|
+
cartosym parse <input-file>
|
|
53
|
+
|
|
54
|
+
# Validate a .cscss or .cs.json file against the grammar/JSON schema
|
|
55
|
+
cartosym validate <input-file>
|
|
56
|
+
|
|
57
|
+
# Full option and format reference
|
|
58
|
+
cartosym --help
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
A few concrete conversions, using the files under [`examples/`](examples/)
|
|
62
|
+
(`examples/sld/` for SLD/SE):
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
cartosym examples/0-basic.cscss -o out.cs.json
|
|
66
|
+
cartosym examples/13-vector-point-circle.cscss --to-format sld -o out.sld
|
|
67
|
+
cartosym examples/sld/1-polygon-fill-stroke.sld --to-format maplibre -o out.json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Python API
|
|
71
|
+
|
|
72
|
+
Most users work through the `cartosym` CLI above. The `cql2` submodule also
|
|
73
|
+
exposes CQL2 (the OGC 21-065r2 query-expression sublanguage that
|
|
74
|
+
CartoSym-CSS embeds in `[ filter ]` selectors and CS-JSON encodes as
|
|
75
|
+
`{"op": …, "args": …}`) as a stable, importable API:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from pycartosym import cql2
|
|
79
|
+
|
|
80
|
+
# CQL2-Text -> expression model
|
|
81
|
+
expr = cql2.parse_text("population > 1000")
|
|
82
|
+
|
|
83
|
+
# Expression model -> CQL2-JSON
|
|
84
|
+
cql2.to_cql2_json(expr)
|
|
85
|
+
# {'op': '>', 'args': [{'property': 'population'}, 1000]}
|
|
86
|
+
|
|
87
|
+
# CQL2-JSON selector dict -> CartoSym-CSS filter text
|
|
88
|
+
cql2.to_cql2_text({
|
|
89
|
+
"op": "and",
|
|
90
|
+
"args": [
|
|
91
|
+
{"op": "=", "args": [{"property": "a"}, 1]},
|
|
92
|
+
{"op": "like", "args": [{"property": "b"}, "x%"]},
|
|
93
|
+
],
|
|
94
|
+
})
|
|
95
|
+
# "a = 1 and b like 'x%'"
|
|
96
|
+
|
|
97
|
+
# Operator/function vocabulary, derived from the CQL2 Pydantic models
|
|
98
|
+
cql2.vocab.SPATIAL_RELATE # 's_relate'
|
|
99
|
+
"s_intersects" in cql2.vocab.KNOWN_CQL2_CALLS # True
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`cql2.parse_tree` is a lower-level entry point for converting an
|
|
103
|
+
already-parsed ANTLR `expression` context (as produced while walking the
|
|
104
|
+
CartoSym-CSS grammar) — most callers want `parse_text`.
|
|
105
|
+
|
|
106
|
+
## Development
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
uv sync --all-extras
|
|
110
|
+
uv run pytest
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pycartosym"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{name = "Maxime Collombin", email = "maxime.collombin@netplus.ch"},
|
|
10
|
+
]
|
|
11
|
+
description = "Lossless transcoding between CartoSym CSS and other encodings for OGC Style & Symbology"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = {file = "LICENSE"}
|
|
14
|
+
requires-python = ">=3.10"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Programming Language :: Python :: 3.14",
|
|
25
|
+
"Topic :: Scientific/Engineering :: GIS",
|
|
26
|
+
]
|
|
27
|
+
keywords = ["gis", "cartography", "symbology", "ogc", "sld", "cartosym"]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"antlr4-python3-runtime>=4.13.0",
|
|
30
|
+
"pydantic>=2.0.0",
|
|
31
|
+
"jsonschema>=4.0.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
dev = [
|
|
36
|
+
"antlr4-tools>=0.2.0",
|
|
37
|
+
"pytest>=7.0.0",
|
|
38
|
+
"pytest-cov>=4.0.0",
|
|
39
|
+
"black",
|
|
40
|
+
"ruff>=0.6",
|
|
41
|
+
"mypy",
|
|
42
|
+
"xmlschema>=3.0",
|
|
43
|
+
]
|
|
44
|
+
sld = [
|
|
45
|
+
"lxml>=4.9.0",
|
|
46
|
+
]
|
|
47
|
+
maplibre = []
|
|
48
|
+
cql2 = [
|
|
49
|
+
"pygeofilter>=0.2.0",
|
|
50
|
+
]
|
|
51
|
+
all = [
|
|
52
|
+
"pycartosym[dev,sld,cql2,maplibre]",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[project.urls]
|
|
56
|
+
Homepage = "https://github.com/maxcollombin/pycartosym"
|
|
57
|
+
Documentation = "https://github.com/maxcollombin/pycartosym/blob/main/README.md"
|
|
58
|
+
Repository = "https://github.com/maxcollombin/pycartosym.git"
|
|
59
|
+
Issues = "https://github.com/maxcollombin/pycartosym/issues"
|
|
60
|
+
|
|
61
|
+
[project.scripts]
|
|
62
|
+
cartosym = "pycartosym.cli:main"
|
|
63
|
+
|
|
64
|
+
[tool.setuptools]
|
|
65
|
+
package-dir = {"" = "src"}
|
|
66
|
+
include-package-data = true
|
|
67
|
+
|
|
68
|
+
[tool.setuptools.packages.find]
|
|
69
|
+
where = ["src"]
|
|
70
|
+
|
|
71
|
+
[tool.setuptools.package-data]
|
|
72
|
+
pycartosym = ["*.md", "schemas/*.json"]
|
|
73
|
+
|
|
74
|
+
[tool.pytest.ini_options]
|
|
75
|
+
testpaths = ["tests"]
|
|
76
|
+
python_files = ["test_*.py"]
|
|
77
|
+
python_functions = ["test_*"]
|
|
78
|
+
addopts = "-v --tb=short"
|
|
79
|
+
filterwarnings = [
|
|
80
|
+
"ignore::DeprecationWarning:pydantic.*",
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
[tool.black]
|
|
84
|
+
line-length = 88
|
|
85
|
+
target-version = ['py310', 'py311', 'py312', 'py313', 'py314']
|
|
86
|
+
extend-exclude = 'src/pycartosym/grammar/generated'
|
|
87
|
+
|
|
88
|
+
[tool.ruff]
|
|
89
|
+
line-length = 88
|
|
90
|
+
target-version = "py310"
|
|
91
|
+
extend-exclude = ["src/pycartosym/grammar/generated"]
|
|
92
|
+
|
|
93
|
+
[tool.ruff.lint]
|
|
94
|
+
# E/F/W: flake8 parity. I: isort parity. UP: pyupgrade (safe on the
|
|
95
|
+
# >=3.10 floor — PEP 604/585 syntax). FIX/TD: no stray TODO/FIXME/HACK
|
|
96
|
+
# left in the tree — a known gap raises NotImplementedError with an inline
|
|
97
|
+
# reason instead. D: pydocstyle (google convention); the undocumented-*
|
|
98
|
+
# rules, D205, and D400/D401/D403/D415 (summary-line punctuation/mood/
|
|
99
|
+
# case) are enforced in `src/` only (see per-file-ignores) — fixed by a
|
|
100
|
+
# manual pass, not an autofix, since a naive one mangles identifiers like
|
|
101
|
+
# `stylingRuleName`.
|
|
102
|
+
# T20: no print() (CLAUDE.md rule — use logging; the CLI itself uses
|
|
103
|
+
# sys.stdout.write/sys.stderr.write for genuine user-facing output,
|
|
104
|
+
# not print()). ERA (commented-out code) is a separate follow-up.
|
|
105
|
+
select = ["E", "F", "W", "I", "UP", "FIX", "TD", "D", "T20"]
|
|
106
|
+
|
|
107
|
+
[tool.ruff.lint.pydocstyle]
|
|
108
|
+
convention = "google"
|
|
109
|
+
|
|
110
|
+
[tool.ruff.lint.per-file-ignores]
|
|
111
|
+
# A test's name and body document what it checks; docstring content and
|
|
112
|
+
# summary-line layout carry little there. T201: golden-snapshot drift
|
|
113
|
+
# diagnostics (test_expression_parser_tree.py) intentionally print to
|
|
114
|
+
# stdout for a human reading pytest output — not a logging substitute.
|
|
115
|
+
"tests/**" = [
|
|
116
|
+
"D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", "D205",
|
|
117
|
+
"D400", "D401", "D403", "D415", "T201",
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
[tool.ruff.lint.isort]
|
|
121
|
+
known-first-party = ["pycartosym"]
|
|
122
|
+
|
|
123
|
+
[tool.mypy]
|
|
124
|
+
python_version = "3.10"
|
|
125
|
+
plugins = ["pydantic.mypy"]
|
|
126
|
+
warn_return_any = true
|
|
127
|
+
warn_unused_configs = true
|
|
128
|
+
ignore_missing_imports = true
|
|
129
|
+
exclude = ["^src/pycartosym/grammar/generated/"]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Lossless transcoding between CartoSym-CSS, CS-JSON and other encodings.
|
|
2
|
+
|
|
3
|
+
This package provides tools for converting between CartoSym CSS, CartoSym
|
|
4
|
+
JSON format, and other cartographic symbology encodings like SLD (Styled
|
|
5
|
+
Layer Descriptor) and MapLibre GL Style.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
# Single source of truth: pyproject.toml's [project] version, read back
|
|
14
|
+
# from the installed distribution's metadata — no hardcoded literal to
|
|
15
|
+
# drift out of sync with it.
|
|
16
|
+
__version__ = version("pycartosym")
|
|
17
|
+
except PackageNotFoundError:
|
|
18
|
+
# Not installed (e.g. run from a source checkout without `uv sync`).
|
|
19
|
+
__version__ = "0.0.0+unknown"
|
|
20
|
+
|
|
21
|
+
__author__ = "Maxime Collombin"
|
|
22
|
+
__email__ = "maxime.collombin@netplus.ch"
|
|
23
|
+
|
|
24
|
+
# Codec registry (lazy — sub-codecs register on first import of .codecs)
|
|
25
|
+
from .codecs import detect_codec, get_codec, list_codecs # noqa: F401
|
|
26
|
+
from .converter import Converter
|
|
27
|
+
from .exceptions import CartoSymError, CartoSymSyntaxError
|
|
28
|
+
from .parser import CartoSymParser
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"CartoSymParser",
|
|
32
|
+
"Converter",
|
|
33
|
+
"CartoSymError",
|
|
34
|
+
"CartoSymSyntaxError",
|
|
35
|
+
"get_codec",
|
|
36
|
+
"detect_codec",
|
|
37
|
+
"list_codecs",
|
|
38
|
+
]
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Abstract Syntax Tree (AST) classes for CartoSym CSS.
|
|
2
|
+
|
|
3
|
+
This module contains the data structures representing parsed CartoSym CSS.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class Variable:
|
|
14
|
+
"""Variable definition in CartoSym CSS AST."""
|
|
15
|
+
|
|
16
|
+
name: str
|
|
17
|
+
value: Any
|
|
18
|
+
type: str | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class StyleSheet:
|
|
23
|
+
"""Root node of a CartoSym CSS stylesheet."""
|
|
24
|
+
|
|
25
|
+
metadata: list[Metadata] = field(default_factory=list)
|
|
26
|
+
styling_rules: StylingRuleList | None = None
|
|
27
|
+
variables: list[Variable] = field(default_factory=list)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class Metadata:
|
|
32
|
+
"""Metadata entry in a stylesheet."""
|
|
33
|
+
|
|
34
|
+
key: str
|
|
35
|
+
value: str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class StylingRuleList:
|
|
40
|
+
"""Collection of styling rules."""
|
|
41
|
+
|
|
42
|
+
rules: list[StylingRule]
|
|
43
|
+
|
|
44
|
+
def __post_init__(self):
|
|
45
|
+
"""Default ``rules`` to an empty list when constructed with ``None``."""
|
|
46
|
+
if self.rules is None:
|
|
47
|
+
self.rules = []
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class StylingRule:
|
|
52
|
+
"""Individual styling rule with selector and properties."""
|
|
53
|
+
|
|
54
|
+
name: str | None = None
|
|
55
|
+
styling_rule_name: str | None = None # explicit stylingRuleName from grammar
|
|
56
|
+
selector: Selector | None = None
|
|
57
|
+
# all selectors (for nested rules)
|
|
58
|
+
selectors: list = field(default_factory=list)
|
|
59
|
+
symbolizer: Symbolizer | None = None
|
|
60
|
+
nested_rules: list[StylingRule] = field(default_factory=list)
|
|
61
|
+
# all property assignments, for post-processing
|
|
62
|
+
property_assignments: list = field(default_factory=list)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class Selector:
|
|
67
|
+
"""Selector for filtering features."""
|
|
68
|
+
|
|
69
|
+
expression: Expression | None = None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class Symbolizer:
|
|
74
|
+
"""Symbolizer defining how features are rendered."""
|
|
75
|
+
|
|
76
|
+
# Basic properties
|
|
77
|
+
visibility: bool | None = None
|
|
78
|
+
opacity: float | None = None
|
|
79
|
+
z_order: int | None = None
|
|
80
|
+
|
|
81
|
+
# Vector symbolizers
|
|
82
|
+
fill: Fill | None = None
|
|
83
|
+
stroke: Stroke | None = None
|
|
84
|
+
marker: Marker | None = None
|
|
85
|
+
label: Label | None = None
|
|
86
|
+
|
|
87
|
+
# Coverage/Raster properties
|
|
88
|
+
single_channel: str | None = None # e.g., "elevation"
|
|
89
|
+
singleChannel: str | None = None # camelCase alternative
|
|
90
|
+
color_channels: Any | None = None # RGB color channels
|
|
91
|
+
colorChannels: Any | None = None # camelCase alternative
|
|
92
|
+
alpha_channel: Any | None = None # Alpha channel
|
|
93
|
+
alphaChannel: Any | None = None # camelCase alternative
|
|
94
|
+
color_map: Any | None = None # Color mapping
|
|
95
|
+
colorMap: Any | None = None # camelCase alternative
|
|
96
|
+
opacity_map: Any | None = None # Opacity mapping
|
|
97
|
+
opacityMap: Any | None = None # camelCase alternative
|
|
98
|
+
hill_shading: dict[str, Any] | None = None # Hill shading config
|
|
99
|
+
hillShading: dict[str, Any] | None = None # camelCase alternative
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class Expression:
|
|
104
|
+
"""Base class for expressions."""
|
|
105
|
+
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class PropertyAssignment:
|
|
111
|
+
"""Assignment of a value to a property."""
|
|
112
|
+
|
|
113
|
+
property_name: str
|
|
114
|
+
value: Any
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass
|
|
118
|
+
class PropertyAssignmentList:
|
|
119
|
+
"""Collection of property assignments."""
|
|
120
|
+
|
|
121
|
+
assignments: list[PropertyAssignment]
|
|
122
|
+
|
|
123
|
+
def __post_init__(self):
|
|
124
|
+
"""Default ``assignments`` to an empty list when constructed with ``None``."""
|
|
125
|
+
if self.assignments is None:
|
|
126
|
+
self.assignments = []
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@dataclass
|
|
130
|
+
class Fill:
|
|
131
|
+
"""Fill styling properties."""
|
|
132
|
+
|
|
133
|
+
color: str | None = None
|
|
134
|
+
opacity: float | None = None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@dataclass
|
|
138
|
+
class Stroke:
|
|
139
|
+
"""Stroke styling properties."""
|
|
140
|
+
|
|
141
|
+
color: str | None = None
|
|
142
|
+
width: float | None = None
|
|
143
|
+
opacity: float | None = None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass
|
|
147
|
+
class Marker:
|
|
148
|
+
"""Marker styling properties."""
|
|
149
|
+
|
|
150
|
+
elements: list | None = None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@dataclass
|
|
154
|
+
class Label:
|
|
155
|
+
"""Label styling properties."""
|
|
156
|
+
|
|
157
|
+
elements: list | None = None
|
|
158
|
+
position: Any | None = None
|
|
159
|
+
opacity: float | None = None
|
|
160
|
+
placement: Any | None = None
|