├── .dockerignore ├── .env ├── .github └── workflows │ ├── main.yaml │ └── sync.yaml ├── .gitignore ├── .gitmodules ├── Dockerfile ├── LICENSE.md ├── Makefile ├── README.md ├── app ├── config.yaml ├── main.py └── start-reload2.sh ├── config.mk.tmpl ├── docker-compose.yaml ├── improvement-ideas.md ├── requirements.txt ├── scripts ├── publish-docker └── test.sh └── tests ├── data ├── input.lyrx └── withicons.lyrx ├── expected └── icon.png ├── integration_test.sh └── tests.py /.dockerignore: -------------------------------------------------------------------------------- 1 | data -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | ESRI_FONT_PATH=~/.local/share/fonts/esri 2 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build-and-test: 7 | runs-on: 8 | ubuntu-20.04 9 | steps: 10 | - name: Checkout repository and submodules 11 | uses: actions/checkout@v2 12 | with: 13 | submodules: recursive 14 | - name: Build Docker image 15 | run: docker build -t lyrx2sld . 16 | - name: Run 17 | run: docker run -d -p 80:80 lyrx2sld 18 | - name: Set up Python 19 | uses: actions/setup-python@v2 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install --upgrade pip 23 | pip install flake8 pytest 24 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 25 | - name: Run Python tests 26 | run: pytest tests/tests.py -v 27 | - name: Integration test 28 | run: ./tests/integration_test.sh 29 | 30 | publish: 31 | if: ${{ github.ref == 'refs/heads/master' }} 32 | runs-on: 33 | ubuntu-20.04 34 | needs: 35 | build-and-test 36 | env: 37 | PATH: /bin:/usr/bin:/usr/local/bin:/home/runner/.local/bin 38 | SUMMON_PROVIDER: /usr/local/bin/gopass 39 | steps: 40 | - name: Checkout repository and submodules 41 | uses: actions/checkout@v2 42 | with: 43 | submodules: recursive 44 | - name: Build Docker image 45 | run: docker build -t camptocamp/lyrx2sld:latest . 46 | - uses: camptocamp/initialise-gopass-summon-action@v1 47 | with: 48 | ci-gpg-private-key: ${{secrets.CI_GPG_PRIVATE_KEY}} 49 | github-gopass-ci-token: ${{secrets.GOPASS_CI_GITHUB_TOKEN}} 50 | - run: scripts/publish-docker --image=lyrx2sld --tag=latest 51 | - run: | 52 | git_hash=$(git rev-parse --short "$GITHUB_SHA") 53 | git_branch=${GITHUB_REF#refs/heads/} 54 | docker tag camptocamp/lyrx2sld:latest camptocamp/lyrx2sld:$git_branch.$git_hash 55 | scripts/publish-docker --image=lyrx2sld --tag=$git_branch.$git_hash 56 | 57 | -------------------------------------------------------------------------------- /.github/workflows/sync.yaml: -------------------------------------------------------------------------------- 1 | name: 'Synchronize bridge-style' 2 | 3 | on: 4 | repository_dispatch: 5 | types: 6 | - bridge-style_updated 7 | 8 | jobs: 9 | sync: 10 | runs-on: 11 | ubuntu-20.04 12 | steps: 13 | - name: Checkout repository and submodules 14 | uses: actions/checkout@v2 15 | with: 16 | token: ${{ secrets.GOPASS_CI_GITHUB_TOKEN }} 17 | submodules: recursive 18 | - name: Update git submodules 19 | run: | 20 | git pull --recurse-submodules 21 | git submodule update --remote --recursive 22 | - name: Build Docker image 23 | run: docker build -t lyrx2sld . 24 | - name: Run 25 | run: docker run -d -p 80:80 lyrx2sld 26 | - name: Set up Python 27 | uses: actions/setup-python@v2 28 | - name: Install dependencies 29 | run: | 30 | python -m pip install --upgrade pip 31 | pip install flake8 pytest 32 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 33 | - name: Run Python tests 34 | run: pytest tests/tests.py -v 35 | - name: Commit update 36 | run: | 37 | git config --global user.name 'c2c-bot-gis-ci' 38 | git config --global user.email 'c2c-bot-gis-ci@noreply.github.com' 39 | git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} 40 | git commit -am "Auto updated submodule references" && git push || echo "No changes to commit" 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | app/__pycache__/ 4 | app/mylog.log 5 | data/ 6 | tests/__pycache__/ 7 | *~ 8 | config.mk 9 | init-db 10 | init-geoserver 11 | docker-compose-up 12 | env/ 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "bridge-style"] 2 | path = bridge-style 3 | url = git@github.com:camptocamp/bridge-style.git 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12 2 | 3 | COPY ./bridge-style /tmp/bridge-style 4 | COPY requirements.txt /tmp/ 5 | RUN pip install /tmp/bridge-style && \ 6 | pip install --disable-pip-version-check --no-cache-dir --requirement=/tmp/requirements.txt && \ 7 | rm --recursive --force /tmp/* 8 | COPY ./app /app 9 | 10 | WORKDIR /app 11 | 12 | CMD ["fastapi", "run", "main.py", "--port", "80"] 13 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021-2023 Camptocamp SA 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include config.mk 2 | 3 | .PHONY: help 4 | help: ## Display this help message 5 | @echo "Usage: make " 6 | @echo 7 | @echo "Available targets:" 8 | @grep --extended-regexp --no-filename '^[a-zA-Z_-]+:.*## ' $(MAKEFILE_LIST) | sort | \ 9 | awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s%s\n", $$1, $$2}' 10 | 11 | docker-compose-up: ## Start docker composition 12 | docker-compose-up: 13 | docker build -t lyrx2sld . 14 | docker-compose up -d 15 | sleep 5 # wait a bit on services 16 | touch $@ 17 | 18 | init-db: ## Create db and schema, add postgis extension 19 | init-db: docker-compose-up 20 | psql -h $(PG_HOST) -p $(PG_PORT) -U $(PG_USER) -c 'CREATE DATABASE $(PG_DATABASE);' 21 | psql -h $(PG_HOST) -p $(PG_PORT) -U $(PG_USER) -d $(PG_DATABASE) -c 'CREATE EXTENSION postgis;' 22 | psql -h $(PG_HOST) -p $(PG_PORT) -U $(PG_USER) -d $(PG_DATABASE) -c 'CREATE SCHEMA '$(PG_SCHEMA)';' 23 | touch $@ 24 | 25 | init-geoserver: ## Create workspace and add postgis datastore to GeoServer 26 | init-geoserver: init-db 27 | curl -u admin:geoserver -POST -H "Content-type: text/xml" -d "$(GEOSERVER_WORKSPACE)" $(GEOSERVER_URL)"rest/workspaces" 28 | curl -u admin:geoserver -XPOST -H "Content-type: text/xml" -d "$$DATASTORE_XML" $(GEOSERVER_URL)"rest/workspaces/"$(GEOSERVER_WORKSPACE)"/datastores" 29 | touch $@ 30 | 31 | .PHONY: serve 32 | serve: ## Start docker composition and initialize DB and GeoServer 33 | serve: init-geoserver 34 | 35 | .PHONY: convert 36 | convert: ## Convert a style from lyrx to sld (input files set in config.mk) and upload it to GeoServer 37 | convert: serve 38 | psql -h $(PG_HOST) -p $(PG_PORT) -U $(PG_USER) -d $(PG_DATABASE) -a -f $(BASE_PATH)/$(SQL_SCRIPT) 39 | curl -H 'Content-Type: application/json' --location -d @$(BASE_PATH)/$(LYRX_FILE) $(LYRX2SLD_URL) -o $(BASE_PATH)/output.zip 40 | curl -u admin:geoserver -XPOST -H "Content-type: application/zip" --data-binary @$(BASE_PATH)/output.zip $(GEOSERVER_URL)rest/styles 41 | 42 | .PHONY: update 43 | update: ## Convert again the lyrx and update the already existing "Default Styler" style. 44 | curl -H 'Content-Type: application/json' --location -d @$(BASE_PATH)/$(LYRX_FILE) $(LYRX2SLD_URL) -o $(BASE_PATH)/output.zip 45 | curl -u admin:geoserver -XPUT -H "Content-type: application/zip" --data-binary @$(BASE_PATH)/output.zip $(GEOSERVER_URL)rest/styles/Default%20Styler 46 | 47 | .PHONY: stop 48 | stop: ## Stop composition 49 | stop: clean-all 50 | rm -f docker-compose-up 51 | docker-compose down 52 | 53 | .PHONY: clean 54 | clean: ## Delete style from GeoServer 55 | curl -u admin:geoserver -XDELETE $(GEOSERVER_URL)rest/styles/Default%20Styler || true 56 | 57 | .PHONY: clean-all 58 | clean-all: ## Reset db and GeoServer 59 | clean-all: clean 60 | rm -f init-db 61 | rm -f init-geoserver 62 | curl -u admin:geoserver -X DELETE $(GEOSERVER_URL)rest/workspaces/$(GEOSERVER_WORKSPACE)?recurse=true -H "accept: application/json" -H "content-type: application/json" 63 | psql -h $(PG_HOST) -p $(PG_PORT) -U $(PG_USER) -c 'DROP DATABASE $(PG_DATABASE);' 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lyrx2sld 2 | lyrx2sld is a REST service for the conversion of ArcGIS Pro styling (format .lyrx) to GeoServer (format .sld, encapsulated in a .zip archive together with legend images, if any). The service encapsulates a styling library (see below for details) and runs in Docker. 3 | 4 | ## Styling library 5 | The service encapsulates the [bridge-style](https://github.com/camptocamp/bridge-style) library, which is written in Python and originally designed to be able to be used in plugins for ArcGis Desktop and QGIS Desktop, with the goal of allowing users to publish layer symbology to GeoServer. 6 | For that purpose, this library uses the [Geostyler](https://github.com/geostyler) JSON representation as a common internal representation format. 7 | To meet the goals of lyrx2sld, this library was enhanced regarding its capabilities of interpreting the Lyrx format. 8 | See the [Cartographic Information Model documentation](https://github.com/Esri/cim-spec/tree/master/docs/v2) for information about Lyrx. 9 | 10 | Note that the styling library is currently not directly related to the [Geostyler projects](https://github.com/geostyler), but its logic might in the future be migrated to TypeScript to be able to function as a Geostyler project. 11 | 12 | ## Local build and deploy 13 | Be sure to clone the repository with the ```--recursive``` option, to also obtain the [bridge-style](https://github.com/camptocamp/bridge-style) as a submodule. If you have already cloned the repository without this option, do a ```git submodule update --remote``` to download the submodule. 14 | ``` 15 | docker build -t lyrx2sld . 16 | docker run --rm -d --name lyrx2sld -p 80:80 lyrx2sld 17 | ``` 18 | 19 | ### Alternative: using image from dockerhub 20 | ``` 21 | docker run --rm -d --name lyrx2sld -p 80:80 camptocamp/lyrx2sld:latest 22 | ``` 23 | 24 | ### Usage 25 | lyrx data should be sent as a file to http://localhost/v1/lyrx2sld/ through a POST request. The converted SLD styling is sent back in the response content (content type: application/x-zip-compressed). Example using `curl`: 26 | ``` 27 | curl --location -d @/path/to/input.lyrx -H 'Content-Type: application/json' -o /path/to/output.zip "http://localhost/v1/lyrx2sld/" 28 | ``` 29 | 30 | Optional request parameter: `replaceesri` to replace ESRI font markers with standard symbols, to be set to `true` or `false` (default): 31 | ``` 32 | curl --location -d @/path/to/input.lyrx -H 'Content-Type: application/json' -o /path/to/output.zip "http://localhost/v1/lyrx2sld/?replaceesri=true" 33 | ``` 34 | Warnings and errors from bridge-style are written to the logs - to view them: 35 | ``` 36 | docker logs lyrx2sld 37 | ``` 38 | 39 | If the conversion fails, the response contains a JSON object (content type: application/json) with the warnings and errors that occured. 40 | 41 | ## Docker composition 42 | In order to test the resulting SLD in GeoServer with postgis layers, this repo also contains a docker-compose file with the following services: 43 | * postgres database (port 5342) 44 | * GeoServer(port 8080) 45 | * lyrx2sld (port 80) 46 | 47 | Requirements: `docker-compose`, `make`, `curl`, `psql` 48 | 49 | The docker-composition is managed through `make` targets. First, copy the template configuration file with `cp config.mk.tmpl config.mk` and edit the variables in `config.mk` were necessary. Start the composition and initialize GeoServer and the DB with 50 | ``` 51 | make serve 52 | ``` 53 | 54 | The GeoServer GUI will be available at http://localhost:8080/geoserver/ (credential admin / geoserver). 55 | 56 | With the `convert` target you can convert a symbology and upload it to GeoServer. First prepare a folder with the lyrx style file and a SQL script with the layer data and set the variables `BASE_PATH`, `SQL_SCRIPT` and `LYRX_FILE` in `config.mk`. Then run 57 | ``` 58 | make convert 59 | ``` 60 | 61 | The SLD file will be saved in the same folder and sent to GeoServer. In the GeoServer GUI, you'll then need to publish the layer and to link it to the new style. The style will be named `Defaut Styler`. 62 | 63 | Further `make` targets are: 64 | - `update` To convert again the style and update the newly created style in GeoServer. 65 | - `clean` To delete the newly created style from GeoServer (works only if no layer use this style). 66 | - `clean-all` To delete the DB and the GeoServer workspace. 67 | - `stop` To stop the composition. 68 | 69 | To get a list of all targets and their description run 70 | ``` 71 | make help 72 | ``` 73 | 74 | If you require ESRI fonts for your styles, they need to be installed on your system and the variable `ESRI_FONT_PATH` in the `.env` file has to point to the correct path. 75 | -------------------------------------------------------------------------------- /app/config.yaml: -------------------------------------------------------------------------------- 1 | version: 1 2 | disable_existing_loggers: false 3 | 4 | formatters: 5 | standard: 6 | format: "%(asctime)s - %(levelname)s - %(message)s" 7 | 8 | handlers: 9 | console: 10 | class: logging.StreamHandler 11 | formatter: standard 12 | level: INFO 13 | stream: ext://sys.stdout 14 | 15 | file: 16 | class: logging.handlers.WatchedFileHandler 17 | formatter: standard 18 | filename: mylog.log 19 | level: INFO 20 | 21 | 22 | loggers: 23 | uvicorn: 24 | error: 25 | propagate: true 26 | 27 | root: 28 | level: INFO 29 | handlers: [console, file] 30 | propagate: no 31 | -------------------------------------------------------------------------------- /app/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import io 3 | import zipfile 4 | 5 | import traceback 6 | import yaml 7 | 8 | from pydantic import BaseModel 9 | from fastapi import FastAPI, status, Response 10 | from fastapi.encoders import jsonable_encoder 11 | from fastapi.responses import JSONResponse 12 | 13 | import logging 14 | 15 | from bridgestyle.arcgis import togeostyler 16 | from bridgestyle.sld import fromgeostyler 17 | 18 | # For debugging only, the IP must be one IP of your machine. 19 | # import pydevd_pycharm 20 | # pydevd_pycharm.settrace('172.17.0.1', port=5678, stdoutToServer=True, stderrToServer=True) 21 | 22 | 23 | class Lyrx(BaseModel): 24 | type: str 25 | version: str 26 | build: int 27 | layers: list[str] | None = None 28 | layerDefinitions: list[dict] 29 | binaryReferences: list[dict] | None = None 30 | elevationSurfaces: list[dict] | None = None 31 | rGBColorProfile: str | None = None 32 | cMYKColorProfile: str | None 33 | 34 | 35 | app = FastAPI() 36 | 37 | LOG = logging.getLogger("app") 38 | with open("config.yaml") as f: 39 | config = yaml.load(f, Loader=yaml.FullLoader) 40 | logging.config.dictConfig(config) 41 | 42 | 43 | @app.post("/v1/lyrx2sld/") 44 | async def lyrx_to_sld(lyrx: Lyrx, replaceesri: bool = False): 45 | 46 | options = {"tolowercase": True, "replaceesri": replaceesri} 47 | warnings = [] 48 | 49 | try: 50 | geostyler, icons, w = togeostyler.convert(lyrx.dict(), options) 51 | warnings.extend(w) 52 | converted, wb = fromgeostyler.convert(geostyler, options) 53 | warnings.extend(w) 54 | 55 | s = io.BytesIO() 56 | z = zipfile.ZipFile(s, "w") 57 | 58 | for icon in icons: 59 | if icon: 60 | z.write(icon, os.path.basename(icon)) 61 | z.writestr("style.sld", converted) 62 | z.close() 63 | 64 | for warning in warnings: 65 | LOG.warning(warning) 66 | 67 | return Response( 68 | content=s.getvalue(), 69 | media_type="application/x-zip-compressed", 70 | headers={"Content-Disposition": "attachment;filename=style.zip"}, 71 | ) 72 | 73 | except Exception as e: 74 | errors = traceback.format_exception(None, e, e.__traceback__) 75 | for error in errors: 76 | LOG.error(error) 77 | return JSONResponse( 78 | status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, 79 | content=jsonable_encoder({"warnings": warnings, "errors": errors}), 80 | ) 81 | -------------------------------------------------------------------------------- /app/start-reload2.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env sh 2 | # FROM https://github.com/tiangolo/uvicorn-gunicorn-docker/tree/master/docker-images, 3 | # Set the last line to add the "reload-dir" params with the path to the modified code 4 | # (otherwise it looks only in /app) 5 | set -e 6 | 7 | if [ -f /app/app/main.py ]; then 8 | DEFAULT_MODULE_NAME=app.main 9 | elif [ -f /app/main.py ]; then 10 | DEFAULT_MODULE_NAME=main 11 | fi 12 | MODULE_NAME=${MODULE_NAME:-$DEFAULT_MODULE_NAME} 13 | VARIABLE_NAME=${VARIABLE_NAME:-app} 14 | export APP_MODULE=${APP_MODULE:-"$MODULE_NAME:$VARIABLE_NAME"} 15 | 16 | HOST=${HOST:-0.0.0.0} 17 | PORT=${PORT:-80} 18 | LOG_LEVEL=${LOG_LEVEL:-info} 19 | 20 | # If there's a prestart.sh script in the /app directory or other path specified, run it before starting 21 | PRE_START_PATH=${PRE_START_PATH:-/app/prestart.sh} 22 | echo "Checking for script in $PRE_START_PATH" 23 | if [ -f $PRE_START_PATH ] ; then 24 | echo "Running script $PRE_START_PATH" 25 | . "$PRE_START_PATH" 26 | else 27 | echo "There is no script $PRE_START_PATH" 28 | fi 29 | 30 | # Start Uvicorn with live reload 31 | exec uvicorn --reload --reload-dir /usr/local/lib/python3.12/site-packages/bridgestyle --reload-dir /app --host $HOST --port $PORT --log-level $LOG_LEVEL "$APP_MODULE" 32 | -------------------------------------------------------------------------------- /config.mk.tmpl: -------------------------------------------------------------------------------- 1 | # Folder, SQL data and lyrx file to convert 2 | BASE_PATH ?= "to be defined" 3 | SQL_SCRIPT ?= "to be defined" 4 | LYRX_FILE ?= "to be defined" 5 | export BASE_PATH 6 | export SQL_SCRIPT 7 | export LYRX_FILE 8 | 9 | # DB connection for the geodata (from local host) 10 | PG_HOST ?= localhost 11 | PG_PORT ?= 5432 12 | PG_USER ?= postgres 13 | PG_PASSWORD ?= postgres 14 | PG_DATABASE ?= geodata 15 | PG_SCHEMA ?= agis 16 | export PG_HOST 17 | export PG_PORT 18 | export PG_USER 19 | export PG_PASSWORD 20 | export PG_DATABASE 21 | export PG_SCHEMA 22 | 23 | # lyrx2sld URL 24 | export LYRX2SLD_URL=http://localhost/v1/lyrx2sld/ 25 | 26 | # Config for GeoServer workspace and datastore 27 | export GEOSERVER_URL=http://localhost:8080/geoserver/ 28 | export GEOSERVER_WORKSPACE=agis 29 | # DB connection (from the GeoServer service) 30 | define DATASTORE_XML 31 | 32 | postgis 33 | 34 | db 35 | $(PG_PORT) 36 | $(PG_DATABASE) 37 | $(PG_SCHEMA) 38 | $(PG_USER) 39 | $(PG_PASSWORD) 40 | postgis 41 | 42 | 43 | endef 44 | export DATASTORE_XML 45 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | 5 | db: 6 | image: postgis/postgis:14-3.2-alpine 7 | environment: 8 | - POSTGRES_PASSWORD=postgres 9 | ports: 10 | - 5432:5432 11 | 12 | geoserver: 13 | image: docker.osgeo.org/geoserver:2.24.2 14 | ports: 15 | - 8080:8080 16 | volumes: 17 | - ${ESRI_FONT_PATH:-/usr/local/share/fonts/esri}:/usr/local/share/fonts/esri:ro 18 | 19 | lyrx2sld: 20 | image: lyrx2sld 21 | command: /app/start-reload2.sh # For debug purpose. 22 | ports: 23 | - 80:80 24 | volumes: 25 | - ./bridge-style/bridgestyle:/usr/local/lib/python3.12/site-packages/bridgestyle # For debug purpose 26 | - ./app:/app # For debug purpose 27 | -------------------------------------------------------------------------------- /improvement-ideas.md: -------------------------------------------------------------------------------- 1 | # List of possible improvements 2 | 3 | ## General 4 | - Add a possibility to log the warnings (currently, they are printed only on errors). 5 | - Add tests. 6 | - Fix the used python version. 7 | - Fix all warnings about style, typo, unused variables, etc. 8 | - Type the code. 9 | 10 | ## ArcGIS to Geostyler-style 11 | - For ArcGIS to Geostyler-style, convert unit from pt to px systematically. 12 | - Refactor the code in several files (instead of a single very long file) 13 | 14 | ## To Geostyler-style 15 | - Strict Geostyle-style: 16 | - Use only (validate ?) variable, type and class existing in geoserver-style. 17 | - Add a custom file to list "workaround" (not existing) variable, type and class. -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi[standard]==0.115.0 2 | pydantic==2.9.2 3 | PyYAML==6.0.2 4 | requests==2.32.3 5 | # For debugging 6 | # pydevd_pycharm~=213.7172.25 7 | -------------------------------------------------------------------------------- /scripts/publish-docker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import os.path 5 | import subprocess 6 | import sys 7 | 8 | 9 | def main(): 10 | 11 | parser = argparse.ArgumentParser(description="Publish Docker images") 12 | parser.add_argument("--image", dest="image", help="The image to be exported") 13 | parser.add_argument("--tag", dest="tag", default="dockerhub", help="Used repository",) 14 | args = parser.parse_args() 15 | image = args.image 16 | tag = args.tag 17 | 18 | sys.stdout.flush() 19 | 20 | cmd = ["docker", "login"] 21 | login = subprocess.check_output(["gopass", "gs/ci/dockerhub/username"]).decode() 22 | password = subprocess.check_output(["gopass", "gs/ci/dockerhub/password"]) 23 | prefix = "" 24 | cmd += ["--username=" + login, "--password-stdin"] 25 | process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) 26 | output, output_err = process.communicate(input=password) 27 | if process.returncode != 0: 28 | if output: 29 | print(output.decode()) 30 | if output_err: 31 | print(output_err.decode()) 32 | sys.exit(1) 33 | 34 | full_image = f"camptocamp/{image}:{tag}" 35 | subprocess.check_call(["docker", "push", full_image]) 36 | 37 | if __name__ == "__main__": 38 | main() 39 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -eu 2 | folders=$(ls data/) 3 | input_file='input.lyrx' 4 | output_dir='/usr/share/geoserver/data_dir/styles/ag/' 5 | for folder in $folders 6 | do 7 | echo "Converting ${folder}" 8 | output_file="${output_dir}${folder}.sld" 9 | curl -d @"data/${folder}/${input_file}" "http://localhost/v1/lyrx2sld/?replaceesri=true" -o "${output_file}" 10 | done -------------------------------------------------------------------------------- /tests/data/input.lyrx: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "CIMLayerDocument", 3 | "version" : "2.6.0", 4 | "build" : 24783, 5 | "layers" : [ 6 | "CIMPATH=gp_arcpy_map/bauinventarobjekte.xml" 7 | ], 8 | "layerDefinitions" : [ 9 | { 10 | "type" : "CIMFeatureLayer", 11 | "name" : "Bauinventarobjekte", 12 | "uRI" : "CIMPATH=gp_arcpy_map/bauinventarobjekte.xml", 13 | "sourceModifiedTime" : { 14 | "type" : "TimeInstant" 15 | }, 16 | "metadataURI" : "CIMPATH=Metadata/0d28655f678866ed7620307eff02ae02.xml", 17 | "useSourceMetadata" : false, 18 | "layerElevation" : { 19 | "type" : "CIMLayerElevationSurface", 20 | "mapElevationID" : "{569ED353-A15E-45E7-A584-726C65D9351F}" 21 | }, 22 | "expanded" : true, 23 | "layerType" : "Operational", 24 | "showLegends" : true, 25 | "visibility" : false, 26 | "displayCacheType" : "Permanent", 27 | "maxDisplayCacheAge" : 5, 28 | "showPopups" : true, 29 | "serviceLayerID" : -1, 30 | "refreshRate" : -1, 31 | "refreshRateUnit" : "esriTimeUnitsSeconds", 32 | "autoGenerateFeatureTemplates" : true, 33 | "featureElevationExpression" : "0", 34 | "featureTable" : { 35 | "type" : "CIMFeatureTable", 36 | "displayField" : "SIGNATUR", 37 | "editable" : true, 38 | "fieldDescriptions" : [ 39 | { 40 | "type" : "CIMFieldDescription", 41 | "alias" : "OBJECTID", 42 | "fieldName" : "OBJECTID", 43 | "numberFormat" : { 44 | "type" : "CIMNumericFormat", 45 | "alignmentOption" : "esriAlignRight", 46 | "alignmentWidth" : 12, 47 | "roundingOption" : "esriRoundNumberOfDecimals", 48 | "roundingValue" : 0 49 | }, 50 | "readOnly" : true, 51 | "visible" : true, 52 | "searchMode" : "Exact" 53 | }, 54 | { 55 | "type" : "CIMFieldDescription", 56 | "alias" : "URL", 57 | "fieldName" : "URL", 58 | "visible" : true, 59 | "searchMode" : "Exact" 60 | }, 61 | { 62 | "type" : "CIMFieldDescription", 63 | "alias" : "Signatur", 64 | "fieldName" : "SIGNATUR", 65 | "visible" : true, 66 | "searchMode" : "Exact" 67 | }, 68 | { 69 | "type" : "CIMFieldDescription", 70 | "alias" : "Titel", 71 | "fieldName" : "TITEL", 72 | "visible" : true, 73 | "searchMode" : "Exact" 74 | }, 75 | { 76 | "type" : "CIMFieldDescription", 77 | "alias" : "Ent_Zeit", 78 | "fieldName" : "ENT_ZEIT", 79 | "visible" : true, 80 | "searchMode" : "Exact" 81 | }, 82 | { 83 | "type" : "CIMFieldDescription", 84 | "alias" : "Adresse", 85 | "fieldName" : "ADRESSE", 86 | "visible" : true, 87 | "searchMode" : "Exact" 88 | }, 89 | { 90 | "type" : "CIMFieldDescription", 91 | "alias" : "Gemeinde", 92 | "fieldName" : "GEMEINDE", 93 | "visible" : true, 94 | "searchMode" : "Exact" 95 | }, 96 | { 97 | "type" : "CIMFieldDescription", 98 | "alias" : "SHAPE", 99 | "fieldName" : "Shape", 100 | "visible" : true, 101 | "searchMode" : "Exact" 102 | }, 103 | { 104 | "type" : "CIMFieldDescription", 105 | "alias" : "E_KOORD", 106 | "fieldName" : "E_KOORD", 107 | "numberFormat" : { 108 | "type" : "CIMNumericFormat", 109 | "alignmentOption" : "esriAlignRight", 110 | "alignmentWidth" : 12, 111 | "roundingOption" : "esriRoundNumberOfDecimals", 112 | "roundingValue" : 0 113 | }, 114 | "visible" : true, 115 | "searchMode" : "Exact" 116 | }, 117 | { 118 | "type" : "CIMFieldDescription", 119 | "alias" : "N_KOORD", 120 | "fieldName" : "N_KOORD", 121 | "numberFormat" : { 122 | "type" : "CIMNumericFormat", 123 | "alignmentOption" : "esriAlignRight", 124 | "alignmentWidth" : 12, 125 | "roundingOption" : "esriRoundNumberOfDecimals", 126 | "roundingValue" : 0 127 | }, 128 | "visible" : true, 129 | "searchMode" : "Exact" 130 | } 131 | ], 132 | "timeFields" : { 133 | "type" : "CIMTimeTableDefinition" 134 | }, 135 | "timeDefinition" : { 136 | "type" : "CIMTimeDataDefinition" 137 | }, 138 | "timeDisplayDefinition" : { 139 | "type" : "CIMTimeDisplayDefinition", 140 | "timeInterval" : 0, 141 | "timeIntervalUnits" : "esriTimeUnitsHours", 142 | "timeOffsetUnits" : "esriTimeUnitsYears" 143 | }, 144 | "dataConnection" : { 145 | "type" : "CIMStandardDataConnection", 146 | "workspaceConnectionString" : "AUTHENTICATION_MODE=DBMS;DATABASE=AGIS_312_Prod;DBCLIENT=sqlserver;DB_CONNECTION_PROPERTIES=sqlA-AGIS-312-Prod;ENCRYPTED_PASSWORD=00022e686f6f3157313934364657414f50714a364a6a65787847324951786a6f364a376b556d4768727859387335553d2a00;INSTANCE=sde:sqlserver: sqlA-AGIS-312-Prod;SERVER=SqlServer;USER=agistools", 147 | "workspaceFactory" : "SDE", 148 | "dataset" : "AGIS.ka_bauinventarobj", 149 | "datasetType" : "esriDTFeatureClass" 150 | }, 151 | "studyAreaSpatialRel" : "esriSpatialRelUndefined", 152 | "searchOrder" : "esriSearchOrderSpatial" 153 | }, 154 | "htmlPopupEnabled" : true, 155 | "htmlPopupFormat" : { 156 | "type" : "CIMHtmlPopupFormat", 157 | "htmlUseCodedDomainValues" : true, 158 | "htmlPresentationStyle" : "TwoColumnTable" 159 | }, 160 | "isFlattened" : true, 161 | "selectable" : true, 162 | "selectionSymbol" : { 163 | "type" : "CIMSymbolReference", 164 | "symbol" : { 165 | "type" : "CIMPointSymbol", 166 | "symbolLayers" : [ 167 | { 168 | "type" : "CIMVectorMarker", 169 | "enable" : true, 170 | "anchorPointUnits" : "Relative", 171 | "dominantSizeAxis3D" : "Z", 172 | "size" : 8, 173 | "billboardMode3D" : "FaceNearPlane", 174 | "frame" : { 175 | "xmin" : -2, 176 | "ymin" : -2, 177 | "xmax" : 2, 178 | "ymax" : 2 179 | }, 180 | "markerGraphics" : [ 181 | { 182 | "type" : "CIMMarkerGraphic", 183 | "geometry" : { 184 | "curveRings" : [ 185 | [ 186 | [ 187 | 1.2246467991473532e-16, 188 | 2 189 | ], 190 | { 191 | "a" : [ 192 | [ 193 | 1.2246467991473532e-16, 194 | 2 195 | ], 196 | [ 197 | 0, 198 | 0 199 | ], 200 | 0, 201 | 1 202 | ] 203 | } 204 | ] 205 | ] 206 | }, 207 | "symbol" : { 208 | "type" : "CIMPolygonSymbol", 209 | "symbolLayers" : [ 210 | { 211 | "type" : "CIMSolidFill", 212 | "enable" : true, 213 | "color" : { 214 | "type" : "CIMRGBColor", 215 | "values" : [ 216 | 0, 217 | 255, 218 | 255, 219 | 100 220 | ] 221 | } 222 | } 223 | ] 224 | } 225 | } 226 | ], 227 | "respectFrame" : true 228 | } 229 | ], 230 | "haloSize" : 1, 231 | "scaleX" : 1, 232 | "angleAlignment" : "Map" 233 | } 234 | }, 235 | "featureCacheType" : "None", 236 | "displayFiltersType" : "ByScale", 237 | "labelClasses" : [ 238 | { 239 | "type" : "CIMLabelClass", 240 | "expression" : "[URL]", 241 | "expressionEngine" : "VBScript", 242 | "featuresToLabel" : "AllVisibleFeatures", 243 | "maplexLabelPlacementProperties" : { 244 | "type" : "CIMMaplexLabelPlacementProperties", 245 | "featureType" : "Point", 246 | "avoidPolygonHoles" : true, 247 | "canOverrunFeature" : true, 248 | "canPlaceLabelOutsidePolygon" : true, 249 | "canRemoveOverlappingLabel" : true, 250 | "canStackLabel" : true, 251 | "connectionType" : "Unambiguous", 252 | "constrainOffset" : "NoConstraint", 253 | "contourAlignmentType" : "Page", 254 | "contourLadderType" : "Straight", 255 | "contourMaximumAngle" : 90, 256 | "enableConnection" : true, 257 | "enablePointPlacementPriorities" : true, 258 | "featureWeight" : 0, 259 | "fontHeightReductionLimit" : 4, 260 | "fontHeightReductionStep" : 0.5, 261 | "fontWidthReductionLimit" : 90, 262 | "fontWidthReductionStep" : 5, 263 | "graticuleAlignmentType" : "Straight", 264 | "keyNumberGroupName" : "Default", 265 | "labelBuffer" : 15, 266 | "labelLargestPolygon" : true, 267 | "labelPriority" : -1, 268 | "labelStackingProperties" : { 269 | "type" : "CIMMaplexLabelStackingProperties", 270 | "stackAlignment" : "ChooseBest", 271 | "maximumNumberOfLines" : 3, 272 | "minimumNumberOfCharsPerLine" : 3, 273 | "maximumNumberOfCharsPerLine" : 24, 274 | "separators" : [ 275 | { 276 | "type" : "CIMMaplexStackingSeparator", 277 | "separator" : " ", 278 | "splitAfter" : true 279 | }, 280 | { 281 | "type" : "CIMMaplexStackingSeparator", 282 | "separator" : ",", 283 | "visible" : true, 284 | "splitAfter" : true 285 | } 286 | ] 287 | }, 288 | "lineFeatureType" : "General", 289 | "linePlacementMethod" : "OffsetCurvedFromLine", 290 | "maximumLabelOverrun" : 36, 291 | "maximumLabelOverrunUnit" : "Point", 292 | "minimumFeatureSizeUnit" : "Map", 293 | "multiPartOption" : "OneLabelPerPart", 294 | "offsetAlongLineProperties" : { 295 | "type" : "CIMMaplexOffsetAlongLineProperties", 296 | "placementMethod" : "BestPositionAlongLine", 297 | "labelAnchorPoint" : "CenterOfLabel", 298 | "distanceUnit" : "Percentage", 299 | "useLineDirection" : true 300 | }, 301 | "pointExternalZonePriorities" : { 302 | "type" : "CIMMaplexExternalZonePriorities", 303 | "aboveLeft" : 4, 304 | "aboveCenter" : 2, 305 | "aboveRight" : 1, 306 | "centerRight" : 3, 307 | "belowRight" : 5, 308 | "belowCenter" : 7, 309 | "belowLeft" : 0, 310 | "centerLeft" : 6 311 | }, 312 | "pointPlacementMethod" : "AroundPoint", 313 | "polygonAnchorPointType" : "GeometricCenter", 314 | "polygonBoundaryWeight" : 0, 315 | "polygonExternalZones" : { 316 | "type" : "CIMMaplexExternalZonePriorities", 317 | "aboveLeft" : 4, 318 | "aboveCenter" : 2, 319 | "aboveRight" : 1, 320 | "centerRight" : 3, 321 | "belowRight" : 5, 322 | "belowCenter" : 7, 323 | "belowLeft" : 8, 324 | "centerLeft" : 6 325 | }, 326 | "polygonFeatureType" : "General", 327 | "polygonInternalZones" : { 328 | "type" : "CIMMaplexInternalZonePriorities", 329 | "center" : 1 330 | }, 331 | "polygonPlacementMethod" : "CurvedInPolygon", 332 | "primaryOffset" : 1, 333 | "primaryOffsetUnit" : "Point", 334 | "removeExtraWhiteSpace" : true, 335 | "repetitionIntervalUnit" : "Map", 336 | "rotationProperties" : { 337 | "type" : "CIMMaplexRotationProperties", 338 | "rotationType" : "Arithmetic", 339 | "alignmentType" : "Straight" 340 | }, 341 | "secondaryOffset" : 100, 342 | "strategyPriorities" : { 343 | "type" : "CIMMaplexStrategyPriorities", 344 | "stacking" : 1, 345 | "overrun" : 2, 346 | "fontCompression" : 3, 347 | "fontReduction" : 4, 348 | "abbreviation" : 5 349 | }, 350 | "thinningDistanceUnit" : "Point", 351 | "truncationMarkerCharacter" : ".", 352 | "truncationMinimumLength" : 1, 353 | "truncationPreferredCharacters" : "aeiou" 354 | }, 355 | "name" : "Standard", 356 | "priority" : 2, 357 | "standardLabelPlacementProperties" : { 358 | "type" : "CIMStandardLabelPlacementProperties", 359 | "featureType" : "Line", 360 | "featureWeight" : "Low", 361 | "labelWeight" : "High", 362 | "numLabelsOption" : "OneLabelPerName", 363 | "lineLabelPosition" : { 364 | "type" : "CIMStandardLineLabelPosition", 365 | "above" : true, 366 | "inLine" : true, 367 | "parallel" : true 368 | }, 369 | "lineLabelPriorities" : { 370 | "type" : "CIMStandardLineLabelPriorities", 371 | "aboveStart" : 3, 372 | "aboveAlong" : 3, 373 | "aboveEnd" : 3, 374 | "centerStart" : 3, 375 | "centerAlong" : 3, 376 | "centerEnd" : 3, 377 | "belowStart" : 3, 378 | "belowAlong" : 3, 379 | "belowEnd" : 3 380 | }, 381 | "pointPlacementMethod" : "AroundPoint", 382 | "pointPlacementPriorities" : { 383 | "type" : "CIMStandardPointPlacementPriorities", 384 | "aboveLeft" : 2, 385 | "aboveCenter" : 2, 386 | "aboveRight" : 1, 387 | "centerLeft" : 3, 388 | "centerRight" : 2, 389 | "belowLeft" : 3, 390 | "belowCenter" : 3, 391 | "belowRight" : 2 392 | }, 393 | "rotationType" : "Arithmetic", 394 | "polygonPlacementMethod" : "AlwaysHorizontal" 395 | }, 396 | "textSymbol" : { 397 | "type" : "CIMSymbolReference", 398 | "symbol" : { 399 | "type" : "CIMTextSymbol", 400 | "blockProgression" : "TTB", 401 | "compatibilityMode" : true, 402 | "depth3D" : 1, 403 | "drawSoftHyphen" : true, 404 | "extrapolateBaselines" : true, 405 | "flipAngle" : 90, 406 | "fontEffects" : "Normal", 407 | "fontEncoding" : "Unicode", 408 | "fontFamilyName" : "Arial", 409 | "fontStyleName" : "Regular", 410 | "fontType" : "Unspecified", 411 | "haloSize" : 1, 412 | "height" : 8, 413 | "hinting" : "Default", 414 | "horizontalAlignment" : "Center", 415 | "kerning" : true, 416 | "letterWidth" : 100, 417 | "ligatures" : true, 418 | "lineGapType" : "ExtraLeading", 419 | "shadowColor" : { 420 | "type" : "CIMRGBColor", 421 | "values" : [ 422 | 0, 423 | 0, 424 | 0, 425 | 100 426 | ] 427 | }, 428 | "symbol" : { 429 | "type" : "CIMPolygonSymbol", 430 | "symbolLayers" : [ 431 | { 432 | "type" : "CIMSolidFill", 433 | "enable" : true, 434 | "color" : { 435 | "type" : "CIMRGBColor", 436 | "values" : [ 437 | 0, 438 | 0, 439 | 0, 440 | 100 441 | ] 442 | } 443 | } 444 | ] 445 | }, 446 | "textCase" : "Normal", 447 | "textDirection" : "LTR", 448 | "verticalAlignment" : "Bottom", 449 | "verticalGlyphOrientation" : "Right", 450 | "wordSpacing" : 100, 451 | "billboardMode3D" : "FaceNearPlane" 452 | } 453 | }, 454 | "useCodedValue" : true, 455 | "visibility" : true, 456 | "iD" : -1 457 | } 458 | ], 459 | "renderer" : { 460 | "type" : "CIMSimpleRenderer", 461 | "patch" : "Default", 462 | "symbol" : { 463 | "type" : "CIMSymbolReference", 464 | "symbol" : { 465 | "type" : "CIMPointSymbol", 466 | "symbolLayers" : [ 467 | { 468 | "type" : "CIMCharacterMarker", 469 | "enable" : true, 470 | "anchorPointUnits" : "Relative", 471 | "dominantSizeAxis3D" : "Y", 472 | "size" : 15, 473 | "billboardMode3D" : "FaceNearPlane", 474 | "characterIndex" : 33, 475 | "fontFamilyName" : "ESRI Default Marker", 476 | "fontStyleName" : "Regular", 477 | "fontType" : "Unspecified", 478 | "scaleX" : 1, 479 | "symbol" : { 480 | "type" : "CIMPolygonSymbol", 481 | "symbolLayers" : [ 482 | { 483 | "type" : "CIMSolidFill", 484 | "enable" : true, 485 | "color" : { 486 | "type" : "CIMRGBColor", 487 | "values" : [ 488 | 255, 489 | 0, 490 | 0, 491 | 100 492 | ] 493 | } 494 | } 495 | ] 496 | }, 497 | "scaleSymbolsProportionally" : true, 498 | "respectFrame" : true 499 | } 500 | ], 501 | "haloSize" : 1, 502 | "scaleX" : 1, 503 | "angleAlignment" : "Map" 504 | } 505 | } 506 | }, 507 | "scaleSymbols" : false, 508 | "snappable" : true 509 | } 510 | ], 511 | "binaryReferences" : [ 512 | { 513 | "type" : "CIMBinaryReference", 514 | "uRI" : "CIMPATH=Metadata/0d28655f678866ed7620307eff02ae02.xml", 515 | "data" : "\r\n20201013143958001.0TRUEBauinventarobjekte\r\n" 516 | } 517 | ], 518 | "elevationSurfaces" : [ 519 | { 520 | "type" : "CIMMapElevationSurface", 521 | "elevationMode" : "BaseGlobeSurface", 522 | "name" : "Ground", 523 | "verticalExaggeration" : 1, 524 | "mapElevationID" : "{569ED353-A15E-45E7-A584-726C65D9351F}", 525 | "color" : { 526 | "type" : "CIMRGBColor", 527 | "values" : [ 528 | 255, 529 | 255, 530 | 255, 531 | 100 532 | ] 533 | }, 534 | "surfaceTINShadingMode" : "Smooth", 535 | "visibility" : true, 536 | "expanded" : true 537 | } 538 | ], 539 | "rGBColorProfile" : "sRGB IEC61966-2-1 noBPC", 540 | "cMYKColorProfile" : "U.S. Web Coated (SWOP) v2" 541 | } -------------------------------------------------------------------------------- /tests/data/withicons.lyrx: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "CIMLayerDocument", 3 | "version" : "2.6.0", 4 | "build" : 24783, 5 | "layers" : [ 6 | "CIMPATH=gp_arcpy_map/fledermausquartiere__zu_ber_cksichtigen_bei_baugesuchen.xml" 7 | ], 8 | "layerDefinitions" : [ 9 | { 10 | "type" : "CIMFeatureLayer", 11 | "name" : "Fledermausquartiere: zu berücksichtigen bei Baugesuchen", 12 | "uRI" : "CIMPATH=gp_arcpy_map/fledermausquartiere__zu_ber_cksichtigen_bei_baugesuchen.xml", 13 | "sourceModifiedTime" : { 14 | "type" : "TimeInstant" 15 | }, 16 | "metadataURI" : "CIMPATH=Metadata/e6f4b66bed8f52c4d56d105d05008983.xml", 17 | "useSourceMetadata" : false, 18 | "expanded" : true, 19 | "layerType" : "Operational", 20 | "showLegends" : true, 21 | "visibility" : true, 22 | "displayCacheType" : "Permanent", 23 | "maxDisplayCacheAge" : 5, 24 | "showPopups" : true, 25 | "serviceLayerID" : -1, 26 | "refreshRate" : -1, 27 | "refreshRateUnit" : "esriTimeUnitsSeconds", 28 | "autoGenerateFeatureTemplates" : true, 29 | "featureTable" : { 30 | "type" : "CIMFeatureTable", 31 | "displayField" : "LOKALNAME", 32 | "editable" : true, 33 | "fieldDescriptions" : [ 34 | { 35 | "type" : "CIMFieldDescription", 36 | "alias" : "OBJECTID", 37 | "fieldName" : "OBJECTID", 38 | "numberFormat" : { 39 | "type" : "CIMNumericFormat", 40 | "alignmentOption" : "esriAlignRight", 41 | "alignmentWidth" : 12, 42 | "roundingOption" : "esriRoundNumberOfDecimals", 43 | "roundingValue" : 0 44 | }, 45 | "readOnly" : true, 46 | "visible" : true, 47 | "searchMode" : "Exact" 48 | }, 49 | { 50 | "type" : "CIMFieldDescription", 51 | "alias" : "Gemeinde", 52 | "fieldName" : "Gemeinde", 53 | "visible" : true, 54 | "searchMode" : "Exact" 55 | }, 56 | { 57 | "type" : "CIMFieldDescription", 58 | "alias" : "Lokalname", 59 | "fieldName" : "Lokalname", 60 | "visible" : true, 61 | "searchMode" : "Exact" 62 | }, 63 | { 64 | "type" : "CIMFieldDescription", 65 | "alias" : "ArtNr", 66 | "fieldName" : "ArtNr", 67 | "numberFormat" : { 68 | "type" : "CIMNumericFormat", 69 | "alignmentOption" : "esriAlignRight", 70 | "alignmentWidth" : 12, 71 | "roundingOption" : "esriRoundNumberOfDecimals", 72 | "roundingValue" : 6 73 | }, 74 | "visible" : true, 75 | "searchMode" : "Exact" 76 | }, 77 | { 78 | "type" : "CIMFieldDescription", 79 | "alias" : "Artname", 80 | "fieldName" : "Artname", 81 | "visible" : true, 82 | "searchMode" : "Exact" 83 | }, 84 | { 85 | "type" : "CIMFieldDescription", 86 | "alias" : "Shape", 87 | "fieldName" : "Shape", 88 | "visible" : true, 89 | "searchMode" : "Exact" 90 | }, 91 | { 92 | "type" : "CIMFieldDescription", 93 | "alias" : "ArtnameLat", 94 | "fieldName" : "ArtnameLat", 95 | "visible" : true, 96 | "searchMode" : "Exact" 97 | }, 98 | { 99 | "type" : "CIMFieldDescription", 100 | "alias" : "QuartiertypKurz", 101 | "fieldName" : "QuartiertypKurz", 102 | "visible" : true, 103 | "searchMode" : "Exact" 104 | }, 105 | { 106 | "type" : "CIMFieldDescription", 107 | "alias" : "Nachweistyp", 108 | "fieldName" : "Nachweistyp", 109 | "visible" : true, 110 | "searchMode" : "Exact" 111 | }, 112 | { 113 | "type" : "CIMFieldDescription", 114 | "alias" : "Koord600", 115 | "fieldName" : "Koord600", 116 | "numberFormat" : { 117 | "type" : "CIMNumericFormat", 118 | "alignmentOption" : "esriAlignRight", 119 | "alignmentWidth" : 12, 120 | "roundingOption" : "esriRoundNumberOfDecimals", 121 | "roundingValue" : 0 122 | }, 123 | "visible" : true, 124 | "searchMode" : "Exact" 125 | }, 126 | { 127 | "type" : "CIMFieldDescription", 128 | "alias" : "Koord200", 129 | "fieldName" : "Koord200", 130 | "numberFormat" : { 131 | "type" : "CIMNumericFormat", 132 | "alignmentOption" : "esriAlignRight", 133 | "alignmentWidth" : 12, 134 | "roundingOption" : "esriRoundNumberOfDecimals", 135 | "roundingValue" : 0 136 | }, 137 | "visible" : true, 138 | "searchMode" : "Exact" 139 | }, 140 | { 141 | "type" : "CIMFieldDescription", 142 | "alias" : "MaximumTiere", 143 | "fieldName" : "MaximumTiere", 144 | "numberFormat" : { 145 | "type" : "CIMNumericFormat", 146 | "alignmentOption" : "esriAlignRight", 147 | "alignmentWidth" : 12, 148 | "roundingOption" : "esriRoundNumberOfDecimals", 149 | "roundingValue" : 6 150 | }, 151 | "visible" : true, 152 | "searchMode" : "Exact" 153 | }, 154 | { 155 | "type" : "CIMFieldDescription", 156 | "alias" : "LetzterWertvonDatum", 157 | "fieldName" : "LetzterWertvonDatum", 158 | "visible" : true, 159 | "searchMode" : "Exact" 160 | }, 161 | { 162 | "type" : "CIMFieldDescription", 163 | "alias" : "Was_ist_zu_tun", 164 | "fieldName" : "Was_ist_zu_tun", 165 | "visible" : true, 166 | "searchMode" : "Exact" 167 | }, 168 | { 169 | "type" : "CIMFieldDescription", 170 | "alias" : "N", 171 | "fieldName" : "N", 172 | "numberFormat" : { 173 | "type" : "CIMNumericFormat", 174 | "alignmentOption" : "esriAlignRight", 175 | "alignmentWidth" : 12, 176 | "roundingOption" : "esriRoundNumberOfDecimals", 177 | "roundingValue" : 0 178 | }, 179 | "visible" : true, 180 | "searchMode" : "Exact" 181 | }, 182 | { 183 | "type" : "CIMFieldDescription", 184 | "alias" : "E", 185 | "fieldName" : "E", 186 | "numberFormat" : { 187 | "type" : "CIMNumericFormat", 188 | "alignmentOption" : "esriAlignRight", 189 | "alignmentWidth" : 12, 190 | "roundingOption" : "esriRoundNumberOfDecimals", 191 | "roundingValue" : 0 192 | }, 193 | "visible" : true, 194 | "searchMode" : "Exact" 195 | } 196 | ], 197 | "timeFields" : { 198 | "type" : "CIMTimeTableDefinition" 199 | }, 200 | "timeDefinition" : { 201 | "type" : "CIMTimeDataDefinition" 202 | }, 203 | "timeDisplayDefinition" : { 204 | "type" : "CIMTimeDisplayDefinition", 205 | "timeInterval" : 0, 206 | "timeIntervalUnits" : "esriTimeUnitsHours", 207 | "timeOffsetUnits" : "esriTimeUnitsYears" 208 | }, 209 | "dataConnection" : { 210 | "type" : "CIMStandardDataConnection", 211 | "workspaceConnectionString" : "ENCRYPTED_PASSWORD=00022e68724455726471596a486a4f75644b6a6e7669536864325a625a652f5451523658464c67374b46536a6167413d2a00;SERVER=SqlServer;INSTANCE=sde:sqlserver:sqlA-AGIS-312-Prod;DBCLIENT=sqlserver;DB_CONNECTION_PROPERTIES=sqlA-AGIS-312-Prod;DATABASE=AGIS_312_Prod;USER=agistools;AUTHENTICATION_MODE=DBMS", 212 | "workspaceFactory" : "SDE", 213 | "dataset" : "AGIS_312_Prod.AGIS.alg_fledermausquar", 214 | "datasetType" : "esriDTFeatureClass" 215 | }, 216 | "studyAreaSpatialRel" : "esriSpatialRelUndefined", 217 | "searchOrder" : "esriSearchOrderSpatial" 218 | }, 219 | "htmlPopupEnabled" : true, 220 | "htmlPopupFormat" : { 221 | "type" : "CIMHtmlPopupFormat", 222 | "htmlUseCodedDomainValues" : true, 223 | "htmlPresentationStyle" : "TwoColumnTable" 224 | }, 225 | "isFlattened" : true, 226 | "selectable" : true, 227 | "selectionSymbol" : { 228 | "type" : "CIMSymbolReference", 229 | "symbol" : { 230 | "type" : "CIMPointSymbol", 231 | "symbolLayers" : [ 232 | { 233 | "type" : "CIMVectorMarker", 234 | "enable" : true, 235 | "anchorPointUnits" : "Relative", 236 | "dominantSizeAxis3D" : "Z", 237 | "size" : 8, 238 | "billboardMode3D" : "FaceNearPlane", 239 | "frame" : { 240 | "xmin" : -2, 241 | "ymin" : -2, 242 | "xmax" : 2, 243 | "ymax" : 2 244 | }, 245 | "markerGraphics" : [ 246 | { 247 | "type" : "CIMMarkerGraphic", 248 | "geometry" : { 249 | "curveRings" : [ 250 | [ 251 | [ 252 | 1.2246467991473532e-16, 253 | 2 254 | ], 255 | { 256 | "a" : [ 257 | [ 258 | 1.2246467991473532e-16, 259 | 2 260 | ], 261 | [ 262 | 0, 263 | 0 264 | ], 265 | 0, 266 | 1 267 | ] 268 | } 269 | ] 270 | ] 271 | }, 272 | "symbol" : { 273 | "type" : "CIMPolygonSymbol", 274 | "symbolLayers" : [ 275 | { 276 | "type" : "CIMSolidFill", 277 | "enable" : true, 278 | "color" : { 279 | "type" : "CIMRGBColor", 280 | "values" : [ 281 | 0, 282 | 255, 283 | 255, 284 | 100 285 | ] 286 | } 287 | } 288 | ] 289 | } 290 | } 291 | ], 292 | "respectFrame" : true 293 | } 294 | ], 295 | "haloSize" : 1, 296 | "scaleX" : 1, 297 | "angleAlignment" : "Map" 298 | } 299 | }, 300 | "featureCacheType" : "None", 301 | "displayFiltersType" : "ByScale", 302 | "labelClasses" : [ 303 | { 304 | "type" : "CIMLabelClass", 305 | "expression" : "[LOKALNAME]", 306 | "expressionEngine" : "VBScript", 307 | "featuresToLabel" : "AllVisibleFeatures", 308 | "maplexLabelPlacementProperties" : { 309 | "type" : "CIMMaplexLabelPlacementProperties", 310 | "featureType" : "Point", 311 | "avoidPolygonHoles" : true, 312 | "canOverrunFeature" : true, 313 | "canPlaceLabelOutsidePolygon" : true, 314 | "canRemoveOverlappingLabel" : true, 315 | "canStackLabel" : true, 316 | "connectionType" : "Unambiguous", 317 | "constrainOffset" : "NoConstraint", 318 | "contourAlignmentType" : "Page", 319 | "contourLadderType" : "Straight", 320 | "contourMaximumAngle" : 90, 321 | "enableConnection" : true, 322 | "enablePointPlacementPriorities" : true, 323 | "featureWeight" : 0, 324 | "fontHeightReductionLimit" : 4, 325 | "fontHeightReductionStep" : 0.5, 326 | "fontWidthReductionLimit" : 90, 327 | "fontWidthReductionStep" : 5, 328 | "graticuleAlignmentType" : "Straight", 329 | "keyNumberGroupName" : "Default", 330 | "labelBuffer" : 15, 331 | "labelLargestPolygon" : true, 332 | "labelPriority" : -1, 333 | "labelStackingProperties" : { 334 | "type" : "CIMMaplexLabelStackingProperties", 335 | "stackAlignment" : "ChooseBest", 336 | "maximumNumberOfLines" : 3, 337 | "minimumNumberOfCharsPerLine" : 3, 338 | "maximumNumberOfCharsPerLine" : 24, 339 | "separators" : [ 340 | { 341 | "type" : "CIMMaplexStackingSeparator", 342 | "separator" : " ", 343 | "splitAfter" : true 344 | }, 345 | { 346 | "type" : "CIMMaplexStackingSeparator", 347 | "separator" : ",", 348 | "visible" : true, 349 | "splitAfter" : true 350 | } 351 | ] 352 | }, 353 | "lineFeatureType" : "General", 354 | "linePlacementMethod" : "OffsetCurvedFromLine", 355 | "maximumLabelOverrun" : 36, 356 | "maximumLabelOverrunUnit" : "Point", 357 | "minimumFeatureSizeUnit" : "Map", 358 | "multiPartOption" : "OneLabelPerPart", 359 | "pointExternalZonePriorities" : { 360 | "type" : "CIMMaplexExternalZonePriorities", 361 | "aboveLeft" : 4, 362 | "aboveCenter" : 2, 363 | "aboveRight" : 1, 364 | "centerRight" : 3, 365 | "belowRight" : 5, 366 | "belowCenter" : 7, 367 | "belowLeft" : 8, 368 | "centerLeft" : 6 369 | }, 370 | "pointPlacementMethod" : "AroundPoint", 371 | "polygonAnchorPointType" : "GeometricCenter", 372 | "polygonBoundaryWeight" : 0, 373 | "polygonFeatureType" : "General", 374 | "polygonPlacementMethod" : "CurvedInPolygon", 375 | "primaryOffset" : 1, 376 | "primaryOffsetUnit" : "Point", 377 | "removeExtraWhiteSpace" : true, 378 | "repetitionIntervalUnit" : "Map", 379 | "secondaryOffset" : 100, 380 | "thinningDistanceUnit" : "Point", 381 | "truncationMarkerCharacter" : ".", 382 | "truncationMinimumLength" : 1, 383 | "truncationPreferredCharacters" : "aeiou" 384 | }, 385 | "name" : "Default", 386 | "priority" : 1, 387 | "textSymbol" : { 388 | "type" : "CIMSymbolReference", 389 | "symbol" : { 390 | "type" : "CIMTextSymbol", 391 | "blockProgression" : "TTB", 392 | "compatibilityMode" : true, 393 | "depth3D" : 1, 394 | "drawSoftHyphen" : true, 395 | "extrapolateBaselines" : true, 396 | "flipAngle" : 90, 397 | "fontEffects" : "Normal", 398 | "fontEncoding" : "Unicode", 399 | "fontFamilyName" : "Arial", 400 | "fontStyleName" : "Regular", 401 | "fontType" : "Unspecified", 402 | "haloSize" : 1, 403 | "height" : 8, 404 | "hinting" : "Default", 405 | "horizontalAlignment" : "Center", 406 | "kerning" : true, 407 | "letterWidth" : 100, 408 | "ligatures" : true, 409 | "lineGapType" : "ExtraLeading", 410 | "shadowColor" : { 411 | "type" : "CIMRGBColor", 412 | "values" : [ 413 | 0, 414 | 0, 415 | 0, 416 | 100 417 | ] 418 | }, 419 | "symbol" : { 420 | "type" : "CIMPolygonSymbol", 421 | "symbolLayers" : [ 422 | { 423 | "type" : "CIMSolidFill", 424 | "enable" : true, 425 | "color" : { 426 | "type" : "CIMRGBColor", 427 | "values" : [ 428 | 0, 429 | 0, 430 | 0, 431 | 100 432 | ] 433 | } 434 | } 435 | ] 436 | }, 437 | "textCase" : "Normal", 438 | "textDirection" : "LTR", 439 | "verticalAlignment" : "Bottom", 440 | "verticalGlyphOrientation" : "Right", 441 | "wordSpacing" : 100, 442 | "billboardMode3D" : "FaceNearPlane" 443 | } 444 | }, 445 | "useCodedValue" : true, 446 | "visibility" : true, 447 | "iD" : -1 448 | } 449 | ], 450 | "renderer" : { 451 | "type" : "CIMUniqueValueRenderer", 452 | "colorRamp" : { 453 | "type" : "CIMRandomHSVColorRamp", 454 | "colorSpace" : { 455 | "type" : "CIMICCColorSpace", 456 | "url" : "Default RGB" 457 | }, 458 | "maxH" : 360, 459 | "minS" : 60, 460 | "maxS" : 80, 461 | "minV" : 60, 462 | "maxV" : 80, 463 | "minAlpha" : 100, 464 | "maxAlpha" : 100 465 | }, 466 | "defaultLabel" : "", 467 | "defaultSymbol" : { 468 | "type" : "CIMSymbolReference", 469 | "symbol" : { 470 | "type" : "CIMPointSymbol", 471 | "symbolLayers" : [ 472 | { 473 | "type" : "CIMCharacterMarker", 474 | "enable" : true, 475 | "anchorPointUnits" : "Relative", 476 | "dominantSizeAxis3D" : "Y", 477 | "size" : 18, 478 | "billboardMode3D" : "FaceNearPlane", 479 | "characterIndex" : 34, 480 | "fontFamilyName" : "ESRI Default Marker", 481 | "fontStyleName" : "Regular", 482 | "fontType" : "Unspecified", 483 | "scaleX" : 1, 484 | "symbol" : { 485 | "type" : "CIMPolygonSymbol", 486 | "symbolLayers" : [ 487 | { 488 | "type" : "CIMSolidFill", 489 | "enable" : true, 490 | "color" : { 491 | "type" : "CIMRGBColor", 492 | "values" : [ 493 | 0, 494 | 0, 495 | 0, 496 | 100 497 | ] 498 | } 499 | } 500 | ] 501 | }, 502 | "scaleSymbolsProportionally" : true, 503 | "respectFrame" : true 504 | } 505 | ], 506 | "haloSize" : 1, 507 | "scaleX" : 1, 508 | "angleAlignment" : "Map" 509 | } 510 | }, 511 | "defaultSymbolPatch" : "Default", 512 | "fields" : [ 513 | "Was_ist_zu_tun" 514 | ], 515 | "groups" : [ 516 | { 517 | "type" : "CIMUniqueValueGroup", 518 | "classes" : [ 519 | { 520 | "type" : "CIMUniqueValueClass", 521 | "label" : "Bitte Kontakt mit Sektion Natur und Landschaft aufnehmen.", 522 | "patch" : "Default", 523 | "symbol" : { 524 | "type" : "CIMSymbolReference", 525 | "symbol" : { 526 | "type" : "CIMPointSymbol", 527 | "symbolLayers" : [ 528 | { 529 | "type" : "CIMPictureMarker", 530 | "enable" : true, 531 | "anchorPointUnits" : "Relative", 532 | "dominantSizeAxis3D" : "Z", 533 | "size" : 16, 534 | "billboardMode3D" : "FaceNearPlane", 535 | "invertBackfaceTexture" : true, 536 | "scaleX" : 1, 537 | "textureFilter" : "Draft", 538 | "url" : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAC4NJREFUeJzt3X+wFWUdx/H3vfxUEEQEMUyhKLOJxCKtUEMrkOgHYQ7lJP0YrWkap182/XDGBn80yoCYYTJjqYOlBRGjlJk2lYhCIZoSgpIWggUiXC5w0wvce/vje64c7t1zzu45u/s8u/t5zXxn9HD2nu+zu885+zy7z/OAiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIS00R398HGAucCpwMjARGAEOB/kC/0t98FWgD9gE7gO3Af4DngX8DnY2nLgU1EDv3TinFycCxQDN2fvYp++8DwFZgSyleBLYBh8J+WK0KMgKYDJwNTALeAQwI+8crOIBVlPXAE6VYC+xp8O9K/owFzirFe4Bx2Jdy1C/2ch1YZfkz8CDwR2B3lD/QF/gq8BespnWlEB3AU8BCYCb2i+ST/jR2UHzU33UCPTQBE4ErgRXYlUda594aYA4wJmyy44EHUkowKA4Cq4DvAW8Lm3SCBgOPAue4TiQGzcBlwFzXiQBHA58AbsMuwV2db9uAz2H7JpLp2OWQq8S7YyNwNW4ry19LuawAJjjMoxHnYJezXcBnHeUwCJgN3I+1VV2eV+3AdaWc6nYUcC32re66onRhbZZvAMc3Uqg6zC3LoRNYDrw75RzqNR64lyP34+gUP78JOA+4E+u4cX0OdQErgdPiLOQZWDvBdcHKa/9S4MOk0z6YXiGPh4BpKeUQ1QRgCVahy3N+NqXPfyNwDdZz6fp86Y424HISOl79gBvovcNdx7PA14AhSRS6ZCjWoKuUwybg68CwBHMIowmrzH+gcq6LEs5hElYxfbnq6I61WE9Y4qZg9zZcF7hntALzsb7xJDwRIodXgV8BH8G+UNIyGvg+4dqMsxL4/P7AJcDjIT4/7egE5pHu8eBEYHWMhYgzDgKLifkaE1gQMY/dwB3ADKwnLG4nAV/BuuSr/br1jJEx5nAU9sv5UoTPTzP2YvvfiQHA7TUSdBmdwDLgnTGVd0YDubRjJ/I1wFTsJmxUo0s5LAD+Xmce/6jjc4MMBr6Nn1cS3bEZeHsjhYyrofId4PqY/lYSurBep6uADQ38neHATuLbbzuwLuytWF/8XqwRCdb1OAS7XDwZO9DDY/jMhVgjtV6DsPbeN2PKJymrgY8S8S55kr5Ienfe640O7NJrbAPlXO9BORqJmXWWuy92OfdfD8pQK+7HbkJ655PYs1aud1CtaAdupL4ep4Ue5F9vdALH1VHmi4DnPMg/TCwh5cZ4VJ8mWoPRZezGLhf6RijfRR7kXW88GaGcAGfib0dMUKwg2rF05jLc76wo8Qx2wzGMEzzIt964MUIZ78C/+13V4k/Yo/CZMQf3Oy1qLCPcPZSNHuRaT3ysRrn6Yo3vVg9yjRIbgGNqlM07zVhjyfXOixr7se7Laj/VizzIM2ocovqTBmdSf9exy2jFBvBl0jDgBdzvxHriSSo/jHixB/lFjb9VKMsxwI/JTruxPDqxjqFMO5tsXcuWxyHsKd6e17ajPcgtatwQcGwuwIahus6t3vhpQJkyyee77WFiEzbss9w/PcgrSlxQlvvQHByTndTXZe2l4cAruN+pjcQh4Icc7mP/mQc5hY0DHB4UdD525951To3G58mZb+F+p8YR67BG4eyI223G7uLfRX2/Ptuxp4RvL+UQZdtHsadt55Pdy93yWI+f428aMhR/RpM1GvuBH4R87y7gUwH7YxbQEmL7g8AV9L47/D6s0oXJ4ZdEr1Q+x6UB+zMXbsb9zk0z9mBTJVUyAXtAsdL2tXppRpCdR0Diip1k7IZgFKfhfgenGZ8JsU+qXarND7H96fg3ci/JmBdin2SarwNr4o41EfZJ0EjF3YQfQnybB+VNK6aG3CexiDwPUAwedvCZLiwMeG0Q9g34ODbDx4ml128JeO9i7PKrGRuxtwa4j+Aph4I+K4+650vLtS/h/lso6Qh6rKMZm+qy/H2bsTvZw+ndu/SB0nbX93h9H8FDiX2YvyzpeCyg3LkzBfc7Oul4OqDcMyu898rSv28qe60DG+s9luBHQJYH/P2fe1DupOPugHInysUl1l4Hn5m2zQGvXVjhvd1dwOXbbMNmR5lB8DGajlWgWp+ZN6mfO6ogyWgJeO2tFd7b/fqugO0rbdOP3hMt7wp4X97sS/sDXVSQdgef6bOulLfLstTPHRcV5E0OPjNtQWPdN1V4b/c0oOVzDXdvX2mbA9h0nuXSnqvYhdTPHRcVJO7J3Hz0loDXllZ475KAbU7C2hjLsUZ6T/cBr4X4zLwpwrnDT3DfG5J0HKL3IkBNwO96vG8jdm/keHp3804ubXd1j9dbCW6b/MuDcicdbeTwIcWe8vTQXLWYHVD27qUkHsOG7XZPARo0ycXNpX9rwuajWon9CgU923WGB+VNK04PKH9ujMf9Dk4rKg1x7amJ4LHgLYRfii7rg5+ixIKQ+ySTok7+nPUIs4rTF6psf1OI7d+F/zNaxhk78W99xVj0wwrnegenGa3Y5U8lE6k+RqaT6ksVjKIYj5j0jKDxNYlIs8EzAOuma8auxUdgB3gc1uicSIRVRjOkFVs1+G7s4ILt90uwhwxrzefUgQ3OmseR9wEmY5O7jYkvVW/swxa7eQZ7QmAr8HLp9YPY5ef2NBLxrUdgFHAu8HHscYpj3aYTqy3YNJ5N2GjAqIv87MIa6m3YE73VBmJlTQfWcXEv9kDnBuzXU6roiz2L9BD5GEet6B1bsKW+41zQp5BOxS5RVFHyEZuwdkQfJFYTsVWaXB9gRX2xA7unk4kZ2LOqCVuy4DXcH3BF+Pgt9S07J3WaQHZnWS9StGNfaOLAMGystuuTQBEc+4EPVTx6korB2OIprk8GxZGxB5hU5bhJigaixrtP0YZ1qIhHRpKPSZnzEBfXOFbiyFlYo9D1CVLk+FHNo5QhebtR8xL27NM014kU1FpslWM9JuKxZrK51l7WoxNb51AyYDLuT5iixeIwB0b88RvcnzRFif3AG8IdFvFF0ZZacBlzQx4T8cwjuD95ihC5nXLIxbxYacrNMsEee5gczwuc9wqyFOv2leTk+kso7xXkf9gClpKMvcCvXSeRpLxXELAhu5KMVfSeAjVXilBBVrpOIMdyv5xeESrITmxglcQv918+RaggUIBvOgfasMVIc60oFeQR1wnk0BpsytNcK0oFyW0/vUOF2KdFqSAvuk4ghwqxT4tSQV4m592RDqiC5EgXtrSyxGer6wTSUJQKAgX5xktRIfZn3obcVnM0MBybSyuXC7CkoBN4DptB5k4KMLTWt+UP0tCMTYb9XmwZgnNL/y+9tWCPk6zCunXXYfc/CqOIFSTICdgMgFNKMcptOs60Y/eMHsSeYXsKa7+JvK4Jm3zgWmwhF9eDkZKOFmw8+YXYLJUikYwHrgNewP3JHFe0AfdgK3mpPSaxaALOA35BdpdeWAt8GRgS874ROcJxwBVYV6frk75WtGM9TtVW2xVJRF9sHtp1uK8IPWM31o4qaoeDeGYG8DTuK0YrMAcYmmxxRaJrAmYBz+PmUmoudjNUxGsDgauwSSPSqBy/J8dzU0l+jcEWr0yqYmzDLu1EMu1SbP7aOCvHPdhzZSK58GZgNY1XjBa0gpPkVD9gEfVXjo3AuNSzFknZ5diEB1EqxwOo61YKZCr2TFSYynErxRq7IwLA+dTuCr7FWXYiHvgglSvJrWjMjgjT6N0muQtVDpHXfZfDlWMddjdeRMosA14BTnGdiIiPBgPvd52EiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIhkw/8B8l/IdWaH33EAAAAASUVORK5CYII=" 539 | } 540 | ], 541 | "haloSize" : 1, 542 | "scaleX" : 1, 543 | "angleAlignment" : "Map" 544 | } 545 | }, 546 | "values" : [ 547 | { 548 | "type" : "CIMUniqueValue", 549 | "fieldValues" : [ 550 | "Bitte Kontakt mit Sektion Natur und Landschaft aufnehmen." 551 | ] 552 | } 553 | ], 554 | "visible" : true 555 | } 556 | ] 557 | } 558 | ], 559 | "polygonSymbolColorTarget" : "Fill" 560 | }, 561 | "scaleSymbols" : true, 562 | "snappable" : true 563 | } 564 | ], 565 | "binaryReferences" : [ 566 | { 567 | "type" : "CIMBinaryReference", 568 | "uRI" : "CIMPATH=Metadata/e6f4b66bed8f52c4d56d105d05008983.xml", 569 | "data" : "\r\n20201013144148001.0TRUEFledermausquartiere: zu berücksichtigen bei Baugesuchen\r\n" 570 | } 571 | ], 572 | "rGBColorProfile" : "sRGB IEC61966-2-1 noBPC", 573 | "cMYKColorProfile" : "U.S. Web Coated (SWOP) v2" 574 | } -------------------------------------------------------------------------------- /tests/expected/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camptocamp/lyrx2sld/38b54fd9c6406bb2e77884e5c36342dc8f4e0a90/tests/expected/icon.png -------------------------------------------------------------------------------- /tests/integration_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -eu 2 | 3 | SERVICE="http://localhost/v1/lyrx2sld/" 4 | INPUT_FILE="tests/data/withicons.lyrx" 5 | OUTPUT_FILE="/tmp/output.zip" 6 | 7 | STATUS_CODE=$(curl --write-out %{http_code} -v -d @$INPUT_FILE -H 'Content-Type: application/json' -o $OUTPUT_FILE $SERVICE) 8 | 9 | if [ -f "$OUTPUT_FILE" ] && [ $STATUS_CODE = 200 ]; then 10 | echo "Output file has been created and request status code is 200" 11 | exit 0 12 | else 13 | echo "The service did not respond as expected" 14 | exit 1 15 | fi 16 | -------------------------------------------------------------------------------- /tests/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from unittest import TestCase 4 | 5 | import json 6 | import io 7 | import os 8 | import zipfile 9 | import requests 10 | from xml.etree import ElementTree 11 | import ast 12 | 13 | APP_ENDPOINT = "http://localhost/v1/lyrx2sld" 14 | 15 | 16 | class SLDParser(): 17 | 18 | def __init__(self, content): 19 | self.xml = content 20 | self.root = ElementTree.fromstring(self.xml) 21 | self.data = {} 22 | self.parse() 23 | 24 | def strip_tag(self, tag): 25 | try: 26 | return tag.split("}")[1] 27 | except IndexError: 28 | return tag 29 | 30 | def get_subelements(self, element): 31 | data = {} 32 | for subelement in element: 33 | tag = self.strip_tag(subelement.tag) 34 | attrib = subelement.attrib 35 | text = (subelement.text or "").strip() 36 | if attrib: 37 | data[tag] = dict() 38 | for item in attrib: 39 | data[tag][self.strip_tag(item)] = attrib[item] 40 | elif text: 41 | try: 42 | data[tag] = ast.literal_eval(text) 43 | except (SyntaxError, ValueError): 44 | data[tag] = text 45 | else: 46 | data[tag] = self.get_subelements(subelement) 47 | return data 48 | 49 | def parse(self): 50 | for element in self.root: 51 | tag = self.strip_tag(element.tag) 52 | self.data[tag] = self.get_subelements(element) 53 | 54 | def as_dict(self): 55 | return self.data 56 | 57 | 58 | def get_style_from_zip_response(content): 59 | with zipfile.ZipFile(io.BytesIO(content)) as z: 60 | with z.open('style.sld') as f: 61 | sld = f.read().decode('utf-8') 62 | data = SLDParser(sld).as_dict() 63 | return data 64 | 65 | def input_test_file(filename): 66 | return os.path.join(os.path.dirname(__file__), "data", filename) 67 | 68 | def expected_test_file(filename): 69 | return os.path.join(os.path.dirname(__file__), "expected", filename) 70 | 71 | class TestService(TestCase): 72 | 73 | def test_point_symbology(self): 74 | with open(input_test_file("input.lyrx")) as f: 75 | obj = json.load(f) 76 | response = requests.post(APP_ENDPOINT, json=obj, timeout=30) 77 | self.assertEqual(response.status_code, 200) 78 | data = get_style_from_zip_response(response.content) 79 | point_symbolizer = data['NamedLayer']['UserStyle']['FeatureTypeStyle']['Rule']['PointSymbolizer'] 80 | self.assertEqual(data['NamedLayer']['Name'], 'Bauinventarobjekte') 81 | self.assertEqual(point_symbolizer['Graphic']['Mark']['WellKnownName'], 'ttf://ESRI Default Marker#0x21') 82 | 83 | def test_point_symbology_replace_esri(self): 84 | with open(input_test_file("input.lyrx")) as f: 85 | obj = json.load(f) 86 | response = requests.post(f"{APP_ENDPOINT}?replaceesri=true", json=obj, timeout=30) 87 | self.assertEqual(response.status_code, 200) 88 | data = get_style_from_zip_response(response.content) 89 | point_symbolizer = data['NamedLayer']['UserStyle']['FeatureTypeStyle']['Rule']['PointSymbolizer'] 90 | self.assertEqual(data['NamedLayer']['Name'], 'Bauinventarobjekte') 91 | self.assertEqual(point_symbolizer['Graphic']['Mark']['WellKnownName'], 'circle') 92 | 93 | def test_icon_conversion(self): 94 | with open(input_test_file("withicons.lyrx"), encoding="utf-8") as f: 95 | obj = json.load(f) 96 | response = requests.post(APP_ENDPOINT, json=obj, timeout=30) 97 | self.assertEqual(response.status_code, 200) 98 | style = get_style_from_zip_response(response.content) 99 | icon_file = style['NamedLayer']['UserStyle']['FeatureTypeStyle']['Rule']['PointSymbolizer']['Graphic']['ExternalGraphic']['OnlineResource']['href'] 100 | with zipfile.ZipFile(io.BytesIO(response.content)) as z: 101 | zipped_files = {zipinfo.filename for zipinfo in z.infolist()} 102 | self.assertEqual({"style.sld", icon_file}, zipped_files) 103 | with open(expected_test_file("icon.png"), "rb") as f: 104 | expected = f.read() 105 | with z.open(icon_file) as f: 106 | output = f.read() 107 | self.assertEqual(expected, output) 108 | --------------------------------------------------------------------------------