Displaying Raster and Vector Cadastre Over 3D Data

In this tutorial we combine 3D data of a Czech village Jenštejn that we made available for this purpose with both raster and vector cadastre provided by State Administration of Land Surveying and Cadastre (ČÚZK) .

This tutorial expects that you have already set up your VTS backend.

Setting up mapproxy resources

For this step, the most important locations are /var/vts/mapproxy/datasets/ where all inputs for mapproxy are stored and /etc/vts/mapproxy/resource.json where you will place configuration snippet for each mapproxy resource.

During resource preparation it is advisible to turn off the mapproxy, so that you have time to correct mistakes in your configuration

sudo /etc/init.d/vts-backend-mapproxy stop

As the whole vts-backend runs under the vts user, it is advisable to switch to the vts user so that all files are created with the right privileges and ownership.

sudo -iu vts

Setting up dynamic surfaces

We need to set up two surfaces - one from SRTM 1 arc sec N50E014 tile for context and second from finer DEM of Jenstejn surroundings.

The SRTM must be converted away from SRTMHGT format e.g. to GeoTiff prior to processing because SRTMHGT format draws georeferencing information from filename.:

$ gdal_translate -of GTiff N50E014.hgt N50E014.tif

To set up surface resources based on DEM from both SRTM DEM and Jenstejn DEM, please follow more detailed instructions in north-carolina or The Peaks and Valleys of Mars tutorials. In this tutorial, it is expected, that you place the data in /var/vts/mapproxy/datasets/srtm and /var/vts/mapproxy/datasets/jenstejn-dem directoriers respectively. Following block contains condensed commands to prepare DEMs for mapproxy:

# create the SRTM DEM dataset
mkdir -p /var/vts/mapproxy/datasets/srtm
cd /var/vts/mapproxy/datasets/srtm
wget http://cdn.melown.com/pub/vts-tutorials/cadastre/N50E014.hgt
gdal_translate -of GTiff N50E014.hgt N50E014.tif

generatevrtwo N50E014.tif overview --resampling dem --tileSize 1024x1024
generatevrtwo N50E014.tif overview.min --resampling min --tileSize 1024x1024
generatevrtwo N50E014.tif overview.max --resampling max --tileSize 1024x1024
ln -s overview/dataset dem
ln -s overview.min/dataset dem.min
ln -s overview.max/dataset dem.max

# create Jensten dataset
mkdir -p /var/vts/mapproxy/datasets/jenstejn-dem
cd /var/vts/mapproxy/datasets/jenstejn-dem
wget http://cdn.melown.com/pub/vts-tutorials/cadastre/jenstejn-dem.tif

generatevrtwo jenstejn-dem.tif overview --resampling dem --tileSize 1024x1024
generatevrtwo jenstejn-dem.tif overview.min --resampling min --tileSize 1024x1024
generatevrtwo jenstejn-dem.tif overview.max --resampling max --tileSize 1024x1024
ln -s overview/dataset dem
ln -s overview.min/dataset dem.min
ln -s overview.max/dataset dem.max

We now need the configuration snippet for the /etc/vts/mapproxy/resource.json file. The lodRange and tileRange values are taken from the mapproxy-calipers tool. Next we need to create tiling metadata based on mapproxy-calipers output.

cd /var/vts/mapproxy/datasets
mapproxy-calipers srtm/dem --referenceFrame melown2015
# > ...
# > gsd: 24.6774
# > range<pseudomerc>: 9,15 15/8829,5484:8874,5556
# > range: 9,15 137,85:138,86
# > position: obj,14.500069,50.500069,float,0.000000,0.000000,-90.000000,0.000000,144822.451449,55.000000
mapproxy-tiling --input srtm --lodRange 9,15 --tileRange 137,85:138,86 --referenceFrame melown2015

mapproxy-calipers jenstejn-dem/dem --referenceFrame melown2015
# > ...
# > gsd: 3.20576
# > range<pseudomerc>: 13,18 18/70840,44352:70871,44380
# > range: 13,18 2213,1386:2214,1386
# > position: obj,14.611388,50.150629,float,0.000000,0.000000,-90.000000,0.000000,7768.350285,55.000000
mapproxy-tiling --input jenstejn-dem --lodRange 13,18 --tileRange 2213,1386:2214,1386 --referenceFrame melown2015

The directory structure in /var/vts/mapproxy/datasets should now look like this:

jenstejn-dem:
 dem -> overview/dataset
 dem.max -> overview.max/dataset
 dem.min -> overview.min/dataset
 jenstejn-dem.tif
 overview
 overview.max
 overview.min
 tiling.melown2015

srtm:
 dem -> ovr/dataset
 dem.max -> ovr.max/dataset
 dem.min -> ovr.min/dataset
 N50E014.tif
 overview
 overview.max
 overview.min
 tiling.melown2015

The final configuration snippets placed into /etc/vts/mapproxy/resources.json should look like (alter the comment, group and id fields):

[{
  "comment": "SRTM 1 arc sec",
  "group": "cadastre",
  "id": "srtm",
  "type": "surface",
  "driver": "surface-dem",
  "credits": [],
  "definition": {
      "dataset": "srtm",
      "geoidGrid": "egm96_15.gtx"
  },
  "referenceFrames": {
      "melown2015": {
          "lodRange": [ 9, 15 ],
          "tileRange": [
              [ 137, 85 ],
              [ 138, 86 ]
          ]
      }
  }
},
{
  "comment": "Jenstejn DEM",
  "group": "cadastre",
  "id": "jenstejn-dem",
  "type": "surface",
  "driver": "surface-dem",
  "credits": [],
  "definition": {
      "dataset": "jenstejn-dem",
      "geoidGrid": "egm96_15.gtx"
  },
  "referenceFrames": {
      "melown2015": {
          "tileRange": [
              [ 2213, 1386 ],
              [ 2214, 1386 ]
          ],
          "lodRange": [ 13, 18 ]
      }
  }
}]

Setting up bound layers

First we will set up boundlayer with orthophoto based on Czech Mapy.cz maps . Because Mapy.cz work as WMTS ins suitable SRS (webmercator), the tiles need not to be processed by VTS Mapproxy. We will therefore configure this bound layer to be used with the tms-raster-remote driver, which will basically just tell the client to use tiles from some particular external service and how to index them. Add following snippet to the outermost array in /etc/vts/mapproxy/resource.json

{
  "comment": "Mapy.cz orthophoto",
  "group": "cadastre",
  "id": "mapy-cz-ophoto",
  "type": "tms",
  "driver": "tms-raster-remote",
  "credits": ["seznamcz"],
  "definition": {
      "remoteUrl": "//m{alt(1,2,3,4)}.mapserver.mapy.cz/ophoto-m/{loclod}-{locx}-{locy}"
  },
  "registry": {
      "credits" : {"seznamcz":{ "id": 103, "notice": "{copy}{Y} Seznam.cz, a.s." }}
  },
  "referenceFrames":
      {
          "melown2015": {
              "tileRange": [
                  [ 137, 85 ],
                  [ 138, 86 ]
              ],
              "lodRange": [
                  9,
                  21
              ]
          }
      }
}

Now we set up transparent bound layer with raster cadastre drawn from WMS at http://services.cuzk.cz/wms/wms.asp . In /var/vts/mapproxy/datasets/cuzk-raster-cadastre create a file cadastre.xml with the following content:

<GDAL_WMS>
 <Service name="WMS">
   <Version>1.1.1</Version>
   <ServerUrl>http://services.cuzk.cz/wms/wms.asp?SERVICE=WMS</ServerUrl>
   <Layers>hranice_parcel_i,obrazy_parcel_i,parcelni_cisla_i</Layers>
   <SRS>EPSG:3857</SRS>
   <ImageFormat>image/png</ImageFormat>
   <Transparent>TRUE</Transparent>
   <BBoxOrder>xyXY</BBoxOrder>
 </Service>
 <DataWindow>
   <UpperLeftX>1320000</UpperLeftX>
   <UpperLeftY>6693000</UpperLeftY>
   <LowerRightX>2113000</LowerRightX>
   <LowerRightY>6140000</LowerRightY>
   <SizeX>1073741824</SizeX>
   <SizeY>748775824</SizeY>
 </DataWindow>
 <BandsCount>4</BandsCount>
 <BlockSizeX>1024</BlockSizeX>
 <BlockSizeY>1024</BlockSizeY>
 <OverviewCount>20</OverviewCount>
</GDAL_WMS>

This is further more discussed in the example srtm-example.

The bound layer will have the same tile range as SRTM DEM because larger is not needed. Thus the mapproxy configuration snippet will be as following:

{
  "comment": "CUZK Raster cadastre",
  "group": "cadastre",
  "id": "cuzk-raster-cadastre",
  "type": "tms",
  "driver": "tms-raster",
  "credits": ["cuzk"],
  "definition": {
      "dataset": "cuzk-raster-cadastre/cadastre.xml",
      "format": "png",
      "transparent": true
  },
  "registry": {
      "credits" : {"cuzk":{ "id": 104, "notice": "{copy}{Y} ČÚZK" }}
  },
  "referenceFrames": {
      "melown2015": {
          "lodRange": [ 9, 21 ],
          "tileRange": [
              [ 137, 85 ],
              [ 138, 86 ]
          ]
      }
  }
}

Alternatively, mapproxy-calipers tool can be used again to obtain for the lodRange and tileRange values.

Setting up vector free layer

We will set up a geodata free layer with parcel borders and parcel numbers. We will use an MBTiles file as the base resource for mapproxy to demotrate the possibility of serving tiled geodata.

First we need to download a ZIP file with shapefiles of Jenstejn cadastal area from ČÚZK website:

cd /tmp
wget http://services.cuzk.cz/shp/ku/epsg-5514/658499.zip
unzip 658499.zip
cd 658499

We are interested in parcel borders and parcel numbers. We will create one MBTiles containing both these layers but first we need to prepare the GeoJSON to create the MBTiles from. Because original data are in the Krovak projection care must be taken when converting coordinates as system definition of Krovak may come with insufficiently precise towgs84 parameter:

cd /tmp/658499
ogr2ogr -f "GeoJson" \
          -s_srs "+proj=krovak +lat_0=49.5 +lon_0=24.83333333333333 +alpha=0 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel \
                  +towgs84=570.8,85.7,462.8,4.998,1.587,5.261,3.56 +units=m +no_defs" \
          -t_srs "+init=epsg:4326" \
          -dialect sqlite \
          -sql "SELECT geometry, TEXT_KM FROM PARCELY_KN_DEF" \
          jenstejn-parcel-numbers.geojson PARCELY_KN_DEF.shp

ogr2ogr -f "GeoJson" \
          -s_srs "+proj=krovak +lat_0=49.5 +lon_0=24.83333333333333 +alpha=0 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel \
                  +towgs84=570.8,85.7,462.8,4.998,1.587,5.261,3.56 +units=m +no_defs" \
          -t_srs "+init=epsg:4326" \
          -dialect sqlite \
          -sql "SELECT geometry FROM HRANICE_PARCEL_L" \
          jenstejn-parcel-borders.geojson HRANICE_PARCEL_L.shp

Now we will merge geojsons into one containing both linestrings and points using merge-geojsons.py from https://gist.github.com/migurski/3759608

python merge-geojsons.py jenstejn-parcel-numbers.geojson \
       jenstejn-parcel-borders.geojson jenstejn-parcel-all.geojson

To create MBTiles we will use MapBox’s opensource tool tippecanoe. To install it, follow the instructions on github. This part may be better done as a non-vts user.

mkdir ~/git
cd git
git clone https://github.com/mapbox/tippecanoe.git
cd tippecanoe
sudo apt-get install build-essential libsqlite3-dev zlib1g-dev
make -j2
sudo make install

We will place MBTiles into /var/vts/mapproxy/datasets/jenstejn-cadastre/ directory. Because simplification makes little sense for cadastre, we will use tippecanoe just to tile features on a single level of detail without any simplification (again as a vts user)

sudo -iu vts
mkdir /var/vts/mapproxy/datasets/jenstejn-cadastre
tippecanoe -o /var/vts/mapproxy/datasets/jenstejn-cadastre/parcels-all.mbtiles \
           -z 16 -Z 16 -B 16 -ps /tmp/658499/jenstejn-parcel-all.geojson

And finally we create a configuration snippet for mapproxy:

{
   "comment": "Data source",
   "group": "cadastre",
   "id": "cuzk-vector-cadastre",
   "type": "geodata",
   "driver": "geodata-vector-tiled",
   "credits": ["cuzk"],
   "definition": {
       "dataset": "jenstejn-cadastre/parcels-all.mbtiles/{loclod}-{locx}-{locy}"
       , "demDataset": "jenstejn-dem"
       , "geoidGrid": "egm96_15.gtx"
       , "format": "geodataJson"
       , "displaySize": 1024
   },
   "registry": {
       "credits" : {"cuzk":{ "id": 104, "notice": "{copy}{Y} ČÚZK" }}
   },
   "referenceFrames":
       {
           "melown2015": {
               "tileRange": [
                   [553, 346],
                   [553, 346]
               ],
               "lodRange": [11, 17]
           }
       }
}

Now you can turn mapproxy back on

sudo /etc/init.d/vts-backend-mapproxy start

And examine the log

less /var/log/vts/mapproxy.log

You should see no errors, only a Ready to serve <resource> line for each defined resource.

Styling the vector cadastre

To give the vector free layer the right look, we will create a style for it which we later apply to the layer in storage view.

Go to /var/vts/store/stylesheet/ and create cuzk-cadastre-style.json with the following contents:

{
 "layers": {
   "parcel-labels": {
     "label": true,
     "label-size": 20,
     "label-source": "$TEXT_KM",
     "zbuffer-offset": [-11,-50,-50],
     "visibility": 350,
     "label-no-overlap" : false
   },
   "lines": {
     "line-width": 0.002,
     "line-width-units": "ratio",
     "line-flat": true,
     "line": true,
     "line-color": [255,255,0,255],
     "zbuffer-offset": [-1,0,-50]
   }
 }
}

That will tell the browser that we want to see parcel borders yellow drawn by line that looks flat (gets thinner when you tilt). Further, when you come close, the parcel numbers will show up. Check the free layers style documentation for further details.

Filling the storage

To work with static True3D data and/or merge various surfaces together, we must first add them to the storage. Storage is administered by tool vts that takes care of adding tilesets to storage and subsequent generation of required Glues.

Important location for this step is /var/vts/store/stage.melown2015 (stage is a traditional name for the main storage). Furthermore, create following directory to hold the 3D resources

mkdir -p /var/vts/store/resources/tilesets

Preparing True3D tilesets

VTS tileset format is suitable for streaming data over the internet but it is firmly bound to given Reference Frame. For True3D data exchange purposes we specified an open, Reference Frame independent, VEF format meant for storing hierarchical georeferenced textured meshes. The VEF format is a preferable entry point for 3D data into VTS.

To get the True3D data for this tutorial, please download Jenstejn (the whole village) and Jenstejn (center) in VEF fromat to some working directory.

Now we will convert both datasets into VTS tileset

mkdir ~/workdir
cd ~/workdir
wget http://cdn.melown.com/pub/vts-tutorials/cadastre/jenstejn.vef.tar
vef2vts --input jenstejn.vef.tar \
        --output /var/vts/store/resources/tilesets/jenstejn-center \
        --tilesetId jenstejn-center --referenceFrame melown2015
wget http://cdn.melown.com/pub/vts-tutorials/cadastre/jenstejn-village.vef.tar
vef2vts --input jenstejn-village.vef.tar \
        --output /var/vts/store/resources/tilesets/jenstejn-village \
        --tilesetId jenstejn-village --referenceFrame melown2015

Adding tilesets into storage

Now we are ready to merge everything in the storage, First we add the bottommost surface from SRTM DEM as remote tileset. The browser will eventually draw the tiles from URL you specify in `vts --add` command.

Note

Network

If you are running vts-backend on different machine than localhost, replace the 127.0.0.1 IP from now on with IP address or hostname of your server. You must be able to access the server through that IP/hostname from the machine on which you plan to run the browser. If your set up uses different ports, change them accordingly.

vts /var/vts/store/stage.melown2015 --add \
    --tileset http://127.0.0.1:8070/mapproxy/melown2015/surface/cadastre/srtm --top

Then add the two Jenstejns as local tilesets - this way the data are only referenced rather than copied into storage which makes the operation faster and saves some space

vts /var/vts/store/stage.melown2015 --add \
    --tileset local:/var/vts/store/resources/tilesets/jenstejn-village --top
vts /var/vts/store/stage.melown2015 --add \
    --tileset local:/var/vts/store/resources/tilesets/jenstejn-center --top

Creating a storage view

As the final step we need to create a Storage view that combines tilesets from our storage and free and bound layer from the mapproxy.

Go to /var/vts/store/map-config and create the file cadastre with the following contents. The hashes are meant as commnets and need to be deleted before saving the file to create a valid JSON.:

{
      "storage": "../stage.melown2015",  # where is our storage
      "tilesets": [                      # tilesets we pick from the storage, all in our case
              "cadastre-srtm",
              "jenstejn-village",
              "jenstejn-center"
      ],
      "credits": { },                    # no additional credit definitions
      "boundLayers": {                   # where to find definition files for bound layers
              "mapy-cz": "/mapproxy/melown2015/tms/cadastre/mapy-cz-ophoto/boundlayer.json",
              "cadastre-raster": "/mapproxy/melown2015/tms/cadastre/cuzk-raster-cadastre/boundlayer.json"
      },
      "freeLayers": {                    # free layers - vector cadastre and tiles mesh as a base for raster cadastre
              "cadastre-vector": "/mapproxy/melown2015/geodata/cadastre/cuzk-vector-cadastre/freelayer.json",
              "jenstejn-dem" : "/mapproxy/melown2015/surface/cadastre/jenstejn-dem/freelayer.json"
      },
      "view": {                          # what combination will be seen when we open storage view with the browser
              "description": "",
              "surfaces": {
                      "cadastre-srtm": ["mapy-cz"],
                      "jenstejn-village": [],
                      "jenstejn-center": []
              },
              "freeLayers": {            # free layers to display - both, they can be toggled through diagnostic console
                      "cadastre-vector" :  { "style" : "/store/stylesheet/cuzk-cadastre-style.json" },
                      "jenstejn-dem" : { "boundLayers": ["cadastre-raster"],
                                          "depthOffset" : [-5, 0, -10] }
              }
      },
      "namedViews": {},
      "position": [                      # initial position of the map (Jenstejn)
              "obj",14.611103581926853,50.152724855605186,"float",0.00,3.16,-70.91,0.00,226.97,45.00
      ],
      "version": 1
}

Convenience version for copy-pasting:

{
      "storage": "../stage.melown2015",
      "tilesets": [
              "cadastre-srtm",
              "jenstejn-village",
              "jenstejn-center"
      ],
      "credits": { },
      "boundLayers": {
              "mapy-cz": "/mapproxy/melown2015/tms/cadastre/mapy-cz-ophoto/boundlayer.json",
              "cadastre-raster": "/mapproxy/melown2015/tms/cadastre/cuzk-raster-cadastre/boundlayer.json"
      },
      "freeLayers": {
              "cadastre-vector": "/mapproxy/melown2015/geodata/cadastre/cuzk-vector-cadastre/freelayer.json",
              "jenstejn-dem" : "/mapproxy/melown2015/surface/cadastre/jenstejn-dem/freelayer.json"
      },
      "view": {
              "description": "",
              "surfaces": {
                      "cadastre-srtm": ["mapy-cz"],
                      "jenstejn-village": [],
                      "jenstejn-center": []
              },
              "freeLayers": {
                      "cadastre-vector" :  { "style" : "/store/stylesheet/cuzk-cadastre-style.json" },
                      "jenstejn-dem" : { "boundLayers": ["cadastre-raster"],
                                          "depthOffset" : [-5, 0, -10] }
              }
      },
      "namedViews": {},
      "position": [
              "obj",14.611103581926853,50.152724855605186,"float",0.00,3.16,-70.91,0.00,226.97,45.00
      ],
      "version": 1
}

After saving you can test if the storage view is valid by running

cd /var/vts/store/map-config
vts --map-config cadastre

If everything is all right, a large JSON with client side map configuration will be printed.

In that case you can open your browser and go to http://127.0.0.1:8070/store/map-config/cadastre to get nice view of Jenstejn. If you press CTRL + SHIFT + D and then SHIFT + V, a console will open when you can toggle various layers and play with other parameters.