CataMap

Catacombs maps using SVG source map with codes inside it.

The program allows to produce:

  • 2D “readable” maps with symbols changed to larger ones, second level shifted to avoid superimposition of corridors, enlarged zooms, shadowing etc.

  • 3D maps to be used in a 3D visualization program, a webGL server, or the CataZoom app.

Requirements

  • Having inkscape installed on the system and available in the PATH. A recent version of inkscape (1.0 at least) is recommended to avoid units and scaling problems. Inkscape is needed for 2D maps renderings, it is not needed for the 3D mode.

  • Either (for 2D maps):

    • the Pillow (PIL) python module (see later)

    • or ImageMagick “convert” tool to convert PNG to JPEG. If Pillow is present, then convert will not be used.

      Warning: https://github.com/ImageMagick/ImageMagick/issues/396 ImageMagick cache (disk limit) size is too small. Edit /etc/ImageMagick-6/policy.xml and change disk resource limit:

      <policy domain="resource" name="memory" value="12GiB"/>
      <policy domain="resource" name="map" value="20GiiB"/>
      <policy domain="resource" name="width" value="50KP"/>
      <policy domain="resource" name="height" value="50KP"/>
      <policy domain="resource" name="area" value="20GiB"/>
      <policy domain="resource" name="disk" value="80GiB"/>
      

Python modules:

These can be installed using the command:

python -m pip install numpy scipy Pillow
  • svg_to_mesh submodule and its requirements (part of this project)

  • xml ElementTree

  • numpy

  • scipy

  • Pillow (PIL) optionally for PNG/JPEG image conversion. Otherwise ImageMagick “convert” tool will be used (see above)

  • pyclipper, used in 2D maps and only when zoomed zones are used. You can install it via pip.

The 3D part has additional requirements:

The maps differences tool (diff_svg) also needs:

  • yaml (pip module pyyaml)

Usage

  • set the python subdirectory of the project in your PYTHONPATH environment variable. Under Unix sh/bash shells, this would be:

    export PYTHONPATH="~/fdc_catamaps/python:$PYTHONPATH"
    

    (it can be set in a .bash_profile or .bashrc init file)

  • get or make the source SVG file with codes inside, for instance plan_14_fdc_2021_04_29.svg

main * go to the directory containing it * run the module catamap as a program:

python -m catamap --2d plan_14_fdc_2021_04_29.svg

It should work using python3 (python2 support has been dropped). The 2D maps options will produce files with suffixes in the current directory: modified .svg files, .pdf and .jpg files.

The 3D maps option will produce meshes in a subdirectory.

Use the commandline with the ‘-h’ option to get all parameters help.

Inkscape SVG files

SVG files may (should) contain additional XML properties that can be set in the XML editor in the Inkscape software. They allow to specify how objects should be parsed, grouped, represented etc. For now they are mainly used in the 3D part, but the 2D part also uses level, label, and private information information.

Codes correspond to properties used in the program, and can be set in XML elements in the SVG file.

Bool properties can be written true, True, 1, or false, False, 0.

Properties are inherited from parent (group/layer) to children. Thus if a layer has the property corridor set to true, all objects in this layer will be marked as corridor.

In the program, elements are grouped into meshes: a mesh will be the concatenation of all elements of the same kind, to avoid having dozens of thousands of mesh files to load. An object “kind” (a group making a single mesh) is identified by several of its properties, but not all. Namely the identifier is:

<label>_<level>_<public/private>_<accessible/inaccessible>[_<category>]

Other properties are not discriminative, so in some conditions may not be taken into account. For instance if a layer defines a label, level, public and accessible states, and marks items as corridors, if one element inside adds a property block, it will be part of the same mesh group anyway, and the block property will be ignored.

Properties list

alt_colors: JSON dict (2D and 3D maps)

color to be used alternatively in maps. This is a string representing a JSON format dictionary. Keys are colorsets names, values are a second level of dictionary which specifies color properties, a bit like for SVG elements style. The default colorset for 3D maps is map_3d. Ex:

{"map_3d": "#cccccc4c"}

see also: Colorsets

arrow: bool (3D maps)

arrows join a text label to a location on map. They are filar meshes. Most of them are just a segment (2 points) but they may contain more points. Points orientation is important as the arrow goes from the text (above the meshes) down to the pointed item below.

arrow_base_height_shift: float (3D maps)

z shift of the base of an arrow element in the upper layer depth. The arrow head shift is specified using height_shift.

block: bool (3D maps)

block elements have a ceiling and walls. Meshes are extruded and tesselated to be filled.

border: bool (3D maps)

not used if I remember…

camera_light: str (3D maps)

if set to off, the camera light (headlamp) is disabled.

category: str (3D maps)

the category string the element will be associated in the 3D views. Categories can be displayed/hidden using buttons or menus in 3D views.

catflap: bool (3D maps)

In catflaps, paths are replaced with striped tubes.

ceil_texture: JSON dict (3D maps)

Texture definition for the ceiling part of elements. See Texturing below for details.

colorsets: JSON dict (2D maps)

used as a property of the metadata layer only, it specifies which default colorset is associated with specific map types, for instance:

{"private": "black", "igc": "igc"}

see also: Colorsets

colorset_inheritance: JSON dict (2D and 3D maps)

used as a property of the metadata layer only, it specifies colorsets which can inherit the colors defined by another, in order to minimally specialize it.

see also: Colorsets

contrast_floor: str (3D maps)

if true (which is the default), corridor or block top and bottom meshes are contrasted compared to wall meshes: the wall colors (given as the color properties) are lightened or darkened before being assigned to floor or ceilings. If you don’t want this, set this property to false. A third value is allowed: if_no_tex, meaning that contrasting colors is enabled only in non-textured mode (if the commandline option --texture is not set).

corridor: bool (3D maps)

corridors have a floor and walls. Meshes are extruded and tesselated to be filled.

date: str (2D maps)

the date text will be replaced with the date of the map build.

default_categories: JSON list (3D maps)

list of categories which are visible by default (checked in the viewer)

depth_map: bool (3D maps)

the layer is a depth map. It should contain a level property to define the level it maps. See Depth maps for details.

floor_texture: JSON dict (3D maps)

Texture definition for the floor part of elements. See Texturing below for details.

glabel: str (2D maps)

“group label” used to replace elements with ones from the legends layer. The glabel is associated to one legends element if the legends element label property has the value of the glabel property of the element plus the duffix _proto. Ex, in the legends:

label: colim_proto

in the map elements:

glabel: colim
gltf_properties: JSON dict (3D maps)

optionally set on texture image elements, they specify how the texture image is mapped and repeaded on the meshes. See Texturing for details.

height_map: bool (3D maps)

the layer is a height map. This is the same as depth_map except that heights are inverted compared to depths, and that a height map may be relative to another depth map (see the relative_to property). All related attributes are the same as for depth_map otherwise.

height_shift: float (3D maps)

z shift of the element (esp. for corridor, block, wall elements, but also wells and arrows). An element with a height shift will not begin on the ground, but above or below as specified.

hidden: bool (2D and 3D maps)

removed from 3D maps.

inaccessible: bool (3D maps)

inaccessible elements will be separated from others, and set in the Inaccessible display category.

item_height: float (3D maps)

height of the element (esp. for corridor, block, wall elements). WARNING: it is item_height, not height as height is already an official SVG attribute for some elements (rectangles for instance).

label: str (2D and 3D maps)

name of the element type

label_alt_colors: JSON dict (2D and 3D maps)

Like alt_colors, except that the dict has an upper-level which keys area object labels. The dict can be applied hierarchically, thus put in a layer. Ex:

{"repetiteur": {"black": {"bg": "#cccccc4c"}}}

see also: Colorsets

legend: bool (2D maps)

set on a layer, indicates that this layer contains the legend symbols and can be searched for replacement symbols (wells, etc) which will replace those in the map to enlarge them and ease view from a bit further.

level: str (2D and 3D maps)

depth level name (sup, inf, surf, tech, metro, esc…). Levels are an “open” property, there is no predefined list of levels, apart for 2 exceptions:

  • items which don’t specify a level are set by default to a level named sup (generally superior corridor level).

  • a surf level represents the ground surface, and is automatically built. It is flat, altitude 0 by default, but can be associated to an altitude map and geolocalisation information. See Altitude maps later.

Levels are associated to depth maps, so for each level used (except surf), a depth_map layer is supposed to be defined. See Depth maps.

main_clip_rect_id: JSON dict (2D maps)

set in the Inkscape metadata layer, it indicates the clip rectangle ID (in the XML elements IDs) for each map type. A default (fallback) one can be given under the key default in the dict. Ex:

{"default": "clip_general", "private": "clip_all"}

so that, in this example, the private map gets a different field of view from the other ones.

map_transform: SVG transformation spec (2D maps)

additional transformation applied to elements, used for instance to shift lower level parts when they are superimposed under an upper level. Without this shift, they would not be seen because they are hidden by the upper level. The transform is specified in the same way as the standard SVG transform property.

This propery can be set to paths, groups, and arrows (which will thus point to a shifted location).

maps_def: JSON dict (2D maps)

In the SVG metadtadata layer. Definition of 2D maps types (to be used with the -m commandline option). See maps types later for details.

marker: str (3D maps)

set on a layer to specify that this layer is a markers layer. Three types of markers are currently recognized: sounds, photos, and lights.

See Markers for more details.

markers_base_url: bool (3D maps)

set on a marker layer to specify that marekrs in this layer have URLS which point to the server directory specified here. Individual markers will be appended to this base URL. The URL may be absolute (https://photos.google.com/xyzsomething) or relative to the map server (photos/photo_dir). If not specified, the default base url is the markers type (photos, sounds), in relative mode.

markers_map: str (3D maps)

Set on a marker layer: correspondence map file name (CSV file) for markers text. See Markers for more details.

non_visibility: str (2D maps)

list of maps types (json-like), for which this layer is removed. The inverse of visibility (see below).

private: bool (2D and 3D maps)

private elements will only be visible if a code is provided

proto_scale: float (2D maps)

Only in the legend layers elements which are prototypes for symbols replacements, this is the scale to be applied when replacing. The default scale is 0.5 (replaced elements will be half the size they have in the legend).

radius: float (**3D maps)

used in a “marker” layer to specify the max radius between the user click and the marker position for it to be triggered. It can be set on the marker layer itself, or on any marker item inside.

See Markers for more details.

relative_to: str (3D maps)

for height maps or depth maps layers, specify to which depth map the height is relative to. The default is surface depth (which means 0 or the ground altitude map).

replace_children: bool (2D maps)

should be set only in replacement symbols in the legend layer (wells signs, etc) to indicate that children of matching elements should be parsed recursively and their children may be replaced also.

shadow: bool (2D maps)

marks the element (or layer…) to have a halo / shadow around it

shadow_scale: float (2D maps)

in the metadata layer, sets a global shadow scale applied to all shadow operations.

symbol: bool (3D maps)

not sure it is used, after all…

symbol_scale: float (3D maps)

in the metadata layer, scale of symbol meshes (markers, fontis, etc)

text: bool (3D maps)

text layers.

texture: JSON dict (3D maps)

Texture definition for elements. See Texturing below for details.

title: bool (3D maps)

The title string(s) will be used and displayed in the web site title.

travel_ref_level: str (3D maps)

depth level used to define the 3D ground level. It is used to adapt the travel speed scale (for 3D controls) depending to the altitude of the camera to this level. Up to now the level is assimilated to its average altitude, but this could be refined to approximate a plane, if needed. The default is the sup level (default level for all elements).

travel_speed_base: float (3D maps)

Travel speed factor for 3D controls at the altitude of the travel_level altitude, which is the minimum speed. The default is 0.03.

travel_speed_alt_factor: float (3D maps)

Scale factor applied to the distance to the travel level to adapt the travel speed: speed increases with this “elevation” according to this factor. The default is: 0.003.

transform_3d: str( 3D maps)

3D transformation, used to rotate an object in space. The spec is similar to SVG transformations specs, except that it has one more dimension, and the “matrix()” spec becomes “matrix4()”, with 12 parameters (columns of the matrix).

upper_level: str (3D maps)

depth level for the top of elements. Only used for elements joining two levels (wells, arrows)

use_height_map: str (3D maps)

When set on a corridor, block or wall layer, it replaces the constant item_height when extruding the height with a height map whose level is given by the value of this property.

visibility: str (2D and 3D maps)

may be either: private: alternative to private: true, or a list of maps types (json-like), for which this layer is kept visible. If such a list is specified, then maps types not listed here will drop the layer. As visibility has now a broader meaning, it is better to specify private: true for private things, to avoid ambiguities.

wall: bool (3D maps)

wall elements have only side walls (no floor or ceiling).

wall_texture: JSON dict (3D maps)

Texture definition for the walls part of elements. See Texturing below for details.

well: bool (3D maps)

wells are replaced with custom elements, which type is the element label (PE, PS, PSh, échelle, sans, P ossements, …). Well elements (or groups, or layers) shoud specify the level and upper_level.

well_read_mode: str (3D maps)

tells if well elements should be read in the XML file as a single “path” element (a circle for instance), or a group (a grop containing a circle, and additional lines). Thus allowed values are path or group.

z_scale: float or “auto” (**3D maps)

Can be set in the SVG metadtadata layer. This scale factor applies to depths and heights in order to match x, y scalings which may be arbitraty. The default is 0.5 and is also arbitrary. If it is set to “auto”, then if lambert93 coordinates are provied in the appropriate layer, the scale factor will be processed according to these “true” coordinates to match x and y scales.

zoom_area_id: str (2D maps)

Identifier for a zoomed area zone layer. Should be present in a layer containing a polygon which defines a source region. The identifier is also present in a target zoom region layer, see zoom_id. See also Zoomed regions for details.

zoom_hidden: bool (2D maps)

Layers marked with this will be excluded from zoomed areas. See also Zoomed regions for details.

zoom_id:* str (2D maps)

Identifier for a zoomed area zone layer. Should be present in a layer containing a rectangle for the target zoomed region. The identifier is also present in a source zoom region polygon layer, see zoom_area_id. See also Zoomed regions for details.

Maps types

Most common types, such as “public”, “private” are (for now at least) builtin in the program, but the maps_def property dict in the metadata layer allows to define new maps types associated with specific filters. Each map type is a dictionary which defines the following:

Ex:

{
    "igcportail": {
        "name": "igcportail",
        "filters": ["igc_private"],
        "shadows": false,
        "do_pdf": false,
        "do_jpg": false,
    },
}
name: str

name of the map type, usually the same as the map type key.

filters: list

list of filters used for this map type. Several filters are available in the program. The filters list is available using the --list-filters option of the commandline.

shadows: bool

if true, shadows are applied in layers or objects which define the shadow property. If false, shadows are disabled.

do_pdf: bool

if false, PDF output is not processed.

do_jpg: bool

if false, JPEG or TIFF bitmap output is not processed.

Depth maps

Depth maps are layers containing depth information, and nothing else.

Each depth information specifies the depth of a specifi point in the map. They can be specified in several ways:

  • a text element: the position of the element (its center if the text is centered) will be used as the position. The text should specify a floating point number which is the depth at this position.

  • a group containing a text element and a segment path: the text is a float number specifying the depth, and the segment (2 points, oriented from the text toward the application point) specifies where the depth applied: the end pont of the segment will be assigned the given depth.

  • a rectangle: this was originally the way to specify depth, but it proved to be difficult to read and is thus obsolete. Prefer the two above methods. In 2 words, the min of the rectangle is the depth, and the position of the rectangle (its center) is the position. Well, better forget it.

Altitude maps

Altitude maps can be built and used to shift all other depth maps, which are supposed to be relative to the surface ground altitude map (for now at least: we may change this later using an upper_level property in depth maps which could be any other map).

To work we require more information, because we need actual altitude maps, and a geolocalisation of the inkscape SVG file.

Geolocalisation of the SVG file

The SVG file should contain a layer named lambert93 which contains, as its name says it, world coordinates in the Lambert 93 coordinates system.

Coordinates can be spacified at any location of the map. At least 3 points are needed to estimate a transformation, but the more points are provided, the more precise the estimation will be. The coordinates transformation between the SVG positions and the Lambert93 coordinates will be estimated as a linear transformation fit to all SVG/Lambert 93 coortinates couples.

A Lambert93 point is specified in this lambert93 layer as a group containing 2 objects:

  • a text item with text being the 2 cooridinates separated by a coma, ex:

    652470.89,6858871.84
    
  • a segment line (2 points, oriented) going from the text toward the application point on the map.

Lambert 93 positions can be found using https://www.geoportail.gouv.fr/carte: on the map, select “tools” on the right, and enable “display coordinates”. Then select for “reference system” the “Lambert 93” mode.

Altitude maps

Altitude maps can be retreived from BDAlti: https://geoservices.ign.fr/documentation/diffusion/telechargement-donnees-libres.html#bd-alti

Then maps have to be converted using tools in catamap.altitude.bdalti. The altitude directory should be located in the same directory as the SVG map file.

Example

See the map: example map The result in 2D is (left: original map, right: result):

_images/example_map.png _images/example_map_imprimable.jpg

In 3D: here

See for instance how wells indications are enlarged in the “printable” 2D map, the inferior level is shifted to allow seing it when it is superimposed with the upper level, while they are still superposed in the 3D map.

Colorsets

Both 2D and 3D maps, while processed, may assign different colorsets to elements, if specified via the --color option. A colorset is a defined by a name. Each element, group, layer, or labelled element can be assigned a different color for each colorset. Colorsets are defined dynamically in the map SVG source file. XML elements having a alt_colors property can both define colorsets and assign colors.

A few “hard-coded” colorsets are expected to exist (even it it is not mandatory) and are used by default:

  • 3D maps use by default the map_3d colorset.

  • 2D maps using “igc” mode (with ICC maps overlayed) use the igc colorset.

The list of colorsets defined in a given SVG map file can be queried using the --list-colorsets option:

python -m catamap --list-colorsets plan_14_fdc_2023_07_31.svg

A map file may specify which colorset is used by each map type in the colorsets property in the metadata layer. See colorsets property.

Colorsets inheritance

Some colorsets may be slight variants to other existing ones. To avoid redefining every element color in the SVG file, it is possible to specify some “colorset inheritances”. This is done using the colorset_inheritance property in the metadata layer (a hidden layer made for SVG / inkscape). This property is a JSON dictionary, with a {'child': 'parent'} shape. The meaning is that a child inherits all its parent colors, and then can override some.

Colors specifications

An alt_colors property applies recursively to all its children. It is a JSON dictionary, whith the followin shape:

  • primary keys are colorset names

  • primary values are color specs

A color spec is normally also a dict, with the following shape:

  • keys are like in SVG elements style, fg (foreground), bg (background), stroke-width

  • values are strings, either containing the RGBA color, or a float value for stroke-width.

  • colorsets used only in 3D may shunt the second level and specify directly a single RGBA color string as colorspec value.

Ex:

{"map_3d": "#c0c0c0ff",
 "igc": {"bg": "#ff0000ff"},
 "black": {"fg": "#ffffffff", "bg": "#c0606060ff", "stroke-width": "0.2"}}

A label_alt_colors property has an additional upper level whick key is the element label, and the primary value is like the alt_colors dict:

{"repetiteur": {"black": {"bg": "#c0c0c0ff"}},
 "marches": {"black": {"fg": "#c0c0c0ff"}}}

Markers

Markers are small marker objects pointing to external links, such as photographs, or sounds. When the user clicks on a marker, or sufficiently close to it in a given radius, it triggers a link to an external resource (an web URL).

Markers are recorded in dedicated layers in the SVG file. Such layer must have a marker property, which value is the type of markers in the layer. Currently two types of markers are handled: photos (links to photographs URLS) or sounds.

Sounds are handled specificly using a media player inside the 3D map, whereas photographs are just web links, and may actually point to any web resource or page (web page, photo file, video, or anything).

A marker layer can have markers_base_url or radius properties, which respectively specify the base URL for all markers, and the max distance to the marker for which a user click will trigger the marker.

The layer contents are similar to depth layers: they are text objects, or groups containing a text object and a line object to make an arrow from the text to the marker exact location. The layer may not have other kind of groups, it is not recursive.

Items in the marker layer are thus text at specified locations. The text is the URL of the resource (sound, photo, …) and is appended to the base URL above. If no markers_base_url is specified, the marker value (type) is used as a relative directory as base URL (sounds/... or photos/...).

Each item may get a specific radius property, which may overload the layer radius. If no radius is specified, a default is used (10., which would correspond to 10 m radisu, depending on the map scale).

If an item text has no file extension, .jpg will be appended by the server in a photos markers layer. This way the map source, in Inkscape, may display a very short, readable, indication, such as a single number: for instance, 123 will be replaced with photos/123.jpg.

On the web server side, photos and sounds directory have to be created and contain the referenced files.

Markers text syntax

Markers text may be a filename (which will be prefixed with the contents of the markers_base_url property, and possibly suffixed with .jpg), or a list of such filenames:

image1.jpg, video2.mp4, image3.jpg

or:

image1, video2.mp4, image3

In addition to the “direct URL” mechanism above, it is also possible to specify, for each markers layer, a “correspondance map” file (using the markers_map property on the layer). This file will be read as a CSV file (with tab separators). Then texts in markers text in the SGV file will go through this map to get a “final” file name for the marker element. A given marker text can be found several times in the file, and then several files will be associated with the marker element. For instance if you use numbers for markers texts, 1, 2, 3 in the SVG map, a markers_map can specify final filenames such as:

20220304_201758.jpg 1
20220304_201802.jpg 2
20220304_201804.jpg 1
20220304_201815.jpg 3
20220304_202652.jpg 2
20220304_202810.jpg 1

will assign to marker 1 the files: 20220304_201758.jpg, 20220304_201804.jpg, 20220304_202810.jpg, and so on. Each of the file names will undergo the prefix/suffix transformations using markers_base_url etc.

Zoomed regions

Zoomed regions may be displayed to represent en enlarged duplicated portion of the original map. To do so we need 3 components, in 3 different layers:

  • a source zoomed region definition. This is done in a layer containing a zoom_area_id property, this layer should contain a single closed and connected polygon, which traces the region to be zoomed. The zoom_area_id` identifier will be matched to a target zoom region.

  • a target zoomed region area definition (where the zoomed copy will be drawn). This is done in a layer containing a zoom_id property (matching a source region definition identifier). This layer should contain a single rectangle, which marks the bounding box of where the zoomed copy of the map will be drawn.

Other layers can be marked with a zoom_hidden property, which indicates that these layers should not be part of zoomed regions.

Lights

Lights may be inserted in the 3D maps. They are actually handled as a special marker type. Thus they are specified in a dedicated layer, using the marker property with the value lights.

Then elements are like markers: either a text element, or a group containing a text and a segment arrow to point at its exact position. The element can contain the property light_props to specify the light characteristics. This property is a JSON dict, with the following items:

type: string

light type: point, directional or spot.

color: list of float

color of the light, as a triplet (RGB) of float values in the range 0.-1.

intensity: float

intensity of the light source, default: 1.0

range: float

range of the light. 0 is infinity, otherwise this value drives the decay of the light with distance from the source.

direction: list of float

for directional lights (directional, spot), direction of the light.

Other properties may be specified (like the spot cone angle) as in https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual/README.md

Texturing

In 3D mode, elements are transformed into meshes. By default meshes have a color but no texture (images mapped on the meshes). It is possible to specify texturing, by using images loaded in a hidden layer of the SVG file, and telling which texture image is associated with given elements, and how it is mapped.

Texturing involves 3 parts: texture images, mesh-image mapping, and texturing properties.

Texture images

They are images inserted in the Inkscape SVG file, and meant to be used for texturing. They are generally inserted into one or several separate layers, which are hidden in normal display modes.

Texture image elements may optionally contain texture mapping properties, in the gltf_properties property (see below, texturing properties).

Mesh-image mapping

Texture may be associated with an individual polygon element, or to a group or a layer, like most other properties. To be textured, an element shoud have either texture, ceil_texture, wall_texture or floor_texture properties defined, or several of them. ceil_texture, wall_texture or floor_texture represent texturing properties for (respectively) the ceiling, walls, or floor part of an element. texture is the default texturing properties used for all of them (walls, ceil, floor) if other parts specifications are not given. Each of those texturing definition properties is a JSON dictionary, structured like the following:

id: string (mandatory)

SVG ID of the texture image to be mapped. The image element itself is generally in a different SVG layer, as explained above.

mapping_method: string (optional)

specifies how the image is mapped on the mesh. Available methods are, for now, geodesic_z and xy. These are described below. The defautl value for ceilings and floors is xy and geodesic_z is the default for walls.

Texture mapping modes

xy:

used for horizontal (or semi-horizontal) meshes. Texture coordinates are the x,y projection (2D) of mesh coordinates, with appropriate scaling factors, in order to have the texture image exactly where it is in the SVG file.

geodesic_z:

used for vertical meshes. Walls are generally not all in the same plane, and can contain bifurcations. To be correctly mapped (as a wallpaper), texture images should follow a geodesic alignment, starting at the first mesh point, in order to follow curves and bifurcations.

Texturing properties

They describe how a texture image is colored, and repeated on a mesh. At the moment, only limited support for properties is implemented, and such properties are only associated with the texture image elements. In the future we could also specify it in the mesh-image mapping. Anyway this is given via the optional gltf_properties property on the texture image element.

We use gltf_properties because these properties are based on the GLTF format. See https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-sampler for details. This property is a JSON dictionary, which may contain the following:

sampler: JSON dict

specified how the image wraps around the mesh texture coordinates. The fll description is fund at: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-sampler, but mainly, the following sub-properties are used:

wrapS: int

1st texture coord mapping. 10497 means REPEAT; 33071 means CLAMP_TO_EDGE; 33648 means MIRRORED_REPEAT. (I did not invent these funny values…)

wrapT: int

2nd texture coord mapping. Same values as for wrapS.

map_to_meshes module

This module is an extension of the svg_to_mesh module and ist main class, SvgToMesh for more specific purposes. This means the former is dedicated to generically read Inkscape files and convert elements to meshes, while the specialized classes here are dedicated to building catacombs maps.

CataSvgToMesh wiil build 3D maps meshes and objects

CataMapTo2DMap will build “cleaner” 2D maps (with some symbols replaced and enlarged, regions zooms and more, and export PDF and JPG versions.

map_to_meshes module API

class catamap.map_to_meshes.CataMapTo2DMap(concat_mesh='bygroup')[source]

Process XML tree to build modified 2D maps

Parameters:

concat_mesh (str) –

concatenation method between multiple paths in SVG file. ‘merge’: merge all paths in a single mesh ‘time’: use mesh timestep to store each path ‘list’: return a list of meshes ‘bygroup’ (default): return a dict of meshes, one for each main

group, paths are concatenated inside each group

static box_in_region(box, region, bbox, verbose=False)[source]

check if a box is totally inside a region, or totally outside, or intersecting

Warning: if 4 corners are inside, the test says inside, whereas it is not always true for a non-convex clip polygon

Returns:

  • 1 (inside)

  • 0 (intersecting)

  • -1 (outside)

ensure_clip_rect(out_xml, rect_id, in_xml)[source]

if rect_id is not in out_xml, then take it from in_xml and copy it in a new layer un out_xml.

replace_filter_element(xml)[source]

Inside replace_elements, this function is called for each xml element, and should return either the element itself (no replacement), or None (element is discarded), or a replaced XML element.

The default method always returns the input element.

class catamap.map_to_meshes.CataSvgToMesh(concat_mesh='list_bygroup', skull_mesh=None, headless=True)[source]

Process XML tree to build 3D meshes

Parameters:

concat_mesh (str) –

concatenation method between multiple paths in SVG file. ‘merge’: merge all paths in a single mesh ‘time’: use mesh timestep to store each path ‘list’: return a list of meshes ‘bygroup’ (default): return a dict of meshes, one for each main

group, paths are concatenated inside each group

extrude(mesh, distance, height_map=None)[source]

This is an overloaded version of the static SvgToMesh.extrude(mesh, distance)

When height_map is given, use this level height map to shift Z positions

filter_element(xml_element, style=None)[source]

Assign a processing function / method to the given element, a cleaning function to be called after the associated sub-tree is processed, and a bool to tell if children should be skipped. This method can be overloaded and is called for each XML tree element. The default implementation returns None, which means that there is no specific processing and the default behavior should happen.

Returns:

proc – proc_callable and clean_callable may be None, meaning that normal processing should happen. The processing callable will be called with 3 arguments: (xml_element, transform_matrix, style_dict). The cleaning callable will be called without arguments. if skip_children is True, children are skipped.

Return type:

(proc_callable, clean_callable, skip_children) or None

get_alt_colors(props, colorset=None, conv=True)[source]

Get backgroud, foreground colors (fill/border)

load_ground_altitude_bdalti(filename)[source]
filename:

json map, to be used with the API module bdalti.py

make_ps_well(center, radius, z, height, well_type, props, faces=8, smooth=True, rotate=0.0)[source]

PS or PE (blue), P ossements (yellow)

read_path(xml_path, trans, style=None)[source]

Read a path element as mesh, apply coords transformations

read_paths(xml)[source]

Parse XML tree and extract meshes, text and other objects

Parameters:

xml_et (XML tree) – obtained using xml.etree.cElementTree.parse(svg_filename)

save_mesh_dict(meshes, dirname, mesh_format='.glb', mesh_wf_format='.glb', json_filename=None, map2d_filename=None)[source]

mesh_format may be a valid mesh extension (“.obj”, “.gii”, “.mesh”) or GLTF (“.gltf” or “.glb”), or None (not saved here).

If GLTF is used a scene dict (JSON) is returned in the output summary under the key “gltf_scene”.

setup_travel_speed()[source]
class catamap.map_to_meshes.DefaultItemProperties[source]

Defaults and constant values.

This is mainly a remaining of the v1 of the software, where fewer things were indicated in the SVG file, and the program was written originally for a single map (GRS, Paris 14e) with hard-coded labels.

class catamap.map_to_meshes.ItemProperties[source]

XML item properties structure, used by CataSvgToMesh. They represent properties read from the XML file elements.

Properties match those documented in Inkscape SVG files, except the height property which is named item_height in XML file elements (because height is already used in SVG).

static remove_word(label, word)[source]

remove word suffix to label (which may end with several variants, _word_0 etc)

catamap.map_to_meshes.scale_georef_points_file(in_filename, out_filename, scale_factor_x, scale_factor_y=None)[source]

Rescale source positions in a QGis .points file in order to adapt to a different resolution

svg_to_mesh module

This modules allows to read an Inkscape SVG file, parse its elements, and convert them to 3D meshes.

svg_to_mesh module API

class catamap.svg_to_mesh.SvgToMesh(concat_mesh='bygroup')[source]

Read SVG, transforms things into meshes

Parameters:

concat_mesh (str) –

concatenation method between multiple paths in SVG file. ‘merge’: merge all paths in a single mesh ‘time’: use mesh timestep to store each path ‘list’: return a list of meshes ‘bygroup’ (default): return a dict of meshes, one for each main

group, paths are concatenated inside each group

filter_element(style=None)[source]

Assign a processing function / method to the given element, a cleaning function to be called after the associated sub-tree is processed, and a bool to tell if children should be skipped. This method can be overloaded and is called for each XML tree element. The default implementation returns None, which means that there is no specific processing and the default behavior should happen.

Returns:

proc – proc_callable and clean_callable may be None, meaning that normal processing should happen. The processing callable will be called with 3 arguments: (xml_element, transform_matrix, style_dict). The cleaning callable will be called without arguments. if skip_children is True, children are skipped.

Return type:

(proc_callable, clean_callable, skip_children) or None

static get_mesh_color(style)[source]

Returns background_color (fill), foreground_color (borders)

get_transform(trans, previous=None, no_3d=False)[source]
Parameters:
  • trans (str or XML element) – if str: transform field in the SVG element. if element: XML element itself

  • previous (np array or None) – parent transform to be composed with

read_circle(xml_path, trans, style=None)[source]

Read a circle element as a mesh

read_path(xml_path, trans, style=None)[source]

Read a path element as mesh, apply coords transformations

read_paths(xml_et)[source]

Parse XML tree and extract meshes, text and other objects

Parameters:

xml_et (XML tree) – obtained using xml.etree.cElementTree.parse(svg_filename)

read_polygon(xml_path, trans, style=None)[source]

Read a polygon element as a mesh

read_rect(xml_path, trans, style=None)[source]

Read a rectangle element as a mesh

replace_filter_element(xml)[source]

Inside replace_elements, this function is called for each xml element, and should return either the element itself (no replacement), or None (element is discarded), or a replaced XML element.

The default method always returns the input element.

save_mesh_dict(meshes, dirname, mesh_format='.obj', mesh_wf_format='.obj', lights=None)[source]

mesh_format may be a valid mesh extension (“.obj”, “.gii”, “.mesh”) or GLTF (“.gltf” or “.glb”), or None (not saved here).

If GLTF is used a scene dict (JSON) is returned in the output summary under the key “gltf_scene”.

transform_path(xml_path, trans)[source]

trans: transform to be applied

transform_style(xml_path, trans)[source]

adapt style in path/rect to scale changes (stroke width etc)

transform_subtree(xml, in_trans, trans, otrans=None)[source]

in_trans: current transform of xml subtree (out of the subtree) trans: transform to be applied otrans: transform in the output subtree. default=in_trans

class catamap.svg_to_mesh.aims[source]

Fake aims-lite module

class AimsTimeSurface_2[source]

Segments mesh (2 points per polygon)

class AimsTimeSurface_3[source]

Triangles mesh (3 points per polygon)

class vector(dtype, shape)[source]

Fixed size vector

diff_svg module

Print differences between two SVG maps.

Uses the yaml module (pip install pyyaml).

The output is a hierarchical dictionary of differences between the two maps. Differences in tags (properties) and children are recorded. Item keys are their “id” property, which should be unique in the files, and are displayed hierarchically.

Used as a program, the output is in YAML format (mostly human readable)

To use it, try:

python -m catamap.diff_svg --h

It will print the command doc and parameters.

diff_svg module API

catamap.diff_svg.diff_element(el1, el2, verbose_depth=0, verbose_depth_max=1)[source]

Compare two SVG XML trees, record differences in both properties and children in a readable dictionary.

BDAlti maps module

Convertion tools for BDAlti maps (https://geoservices.ign.fr/documentation/diffusion/telechargement-donnees-libres.html#bd-alti) and their use in catamap.

API

catamap.altitude.bdalti.build_meta_table(ima_fnames)[source]

build the metadata table from converted .ima files

ima_fnames: pattern with a “%s”, same as out_fnames in convert_raw_maps()

catamap.altitude.bdalti.convert_raw_map(fname, out_fname)[source]

convert one raw map from bdalti (.asc) to .ima format

catamap.altitude.bdalti.convert_raw_maps(fnames, out_fnames)[source]

convert all raw data files from bdalti (.asc) to .ima format

fnames:

input files pattern (used with glob.glob())

out_fnames:

output files pattern, should contain a “%s” pattern

catamap.altitude.bdalti.get_map_image(xi, yi, meta_map, base)[source]

get image at coords xi, yi in the map table. Lazy load and cache it.

catamap.altitude.bdalti.get_z(x, y, meta_map, base, background_z=-99999.0)[source]

get the altitude from (x, y) coords in Lambert93 coords

TO DO

To do in the future