Skip to main content
Version: 2.2.0

Modlet

The GIS modlet is designed such that map rendering engines can be swapped without having to change the usage of the web components. This is achieved by having web components that interfact with the rendering engine through a standardized interface.

GIS Modlet Components

Types

The GIS modlet exports its types under the Gis namespace, you can find this file in src/lib/aoh/gis/types.d.ts.

When referring to types in this documentation, you will often see the them prefixed with the "Gis" namespace, for example, Gis.GeoEntityType - this refers to types that can be found in the file mentioned above.

Components

Under the src/lib/aoh/gis/components folder, you'll find these ready-to-use Svelte components.

note

Other components you find here that are not mentioned in these documentation are either still in-development and not ready for use, or meant for internal use only, you may however use them at your own risk.

Map Component

The Map component is the heart of the GIS modlet, it contains all the glue to tie the Map components to the rendering engine. The map must always be wrapped with an engine provider. At present, we only have one engine provider (CesiumMapEngineProvider).

A bindable Gis.GisMap object is exposed if you need to get a reference to the map if necessary, but we recommend you create more Svelte Components that get the Gis.GisMap from the Svelte context instead if you want to write reusable components that could be contributed to the GIS modlet in the future.

+page.svelte
<script lang="ts">
import Map from "$lib/aoh/gis/components/Map/index.svelte";
import CesiumMapEngineProvider from "$lib/aoh/gis/components/engines/CesiumMapEngineProvider/index.svelte";

let map: Gis.GisMap;

...
</script>

<CesiumMapEngineProvider>
<Map bind:map {...} >
...
</Map>
</CesiumMapEngineProvider>

Example

Map Component
<Map
initial_camera_view={{
position: [103.8189, 1.3521] // position of Singapore
zoom: 8 // see `Gis.ZoomLevel` for more information
}}
rtus_seh_url={"http://rtus-seh.example.com"}
rtus_map_name={"gis"}
user_id={data.user?.sub}
tenant_id={data.user?.active_tenant?.tenant_id}
>
...
</Map>

initial_camera_view

This property expects a Gis.CameraView, which contains all the necessary information to position and point a camera in the map.

rtus_seh_url

The URL to the RTUS - SEH service - this for the SSE connection that will continually update the map with entities.

rtus_map_name

The name of the RTUS map that stores all the GIS state: this is the same map name as what you have declared in the GIS service.

See gis.rtus.map_name and PUBLIC_GIS_RTUS_MAP_NAME

user_id

Currently not actually used for any purpose but checked during map initialization for legacy reasons. Must not be a falsy value for the map to be initialized. Just pass in any truthy value to proceed.

tenant_id

The tenant_id of the RTUS map to subscribe to - you should be using the tenant_id of the user for this property.


CesiumMapEngineProvider Component

The implementation of the interface can be found within the src/lib/aoh/gis/components/engines folder. We currently support the CesiumJS render.


MapBaseLayerProvider Component

The MapBaseLayerProvider provides the map with base layers. This component expects one or more base layer sources to be provided as children. See MapXyzSourceProvider.

Examples:

MapBaseLayerProvider Component
<MapBaseLayerProvider layer_name="Street" is_visible={true} options={{ brightness: 0.5 }}>
<MapXyzSourceProvider url={`https://tile.openstreetmap.org/{z}/{x}/{y}.png`}></MapXyzSourceProvider>
</MapBaseLayerProvider>

You can group multiple sources under one layer to control their visiblity together. These sources will all appear as just one layer in the Layer Manager UI. The order that they appear will be based on the order they are instantiated in code here.

MapBaseLayerProvider with multiple source providers
<MapBaseLayerProvider layer_name="Street" is_visible={true} options={{ brightness: 0.5 }}>
<MapXyzSourceProvider
url={`https://api.maptiler.com/tiles/hillshade/{z}/{x}/{y}.webp?key=0123456789`}
></MapXyzSourceProvider>
<MapXyzSourceProvider url={`https://tile.openstreetmap.org/{z}/{x}/{y}.png`}></MapXyzSourceProvider>
</MapBaseLayerProvider>

layer_name

The name of the layer, this is used by the MapLayerManager as a label for toggling the visibility.

is_visible

A boolean true or false value to determine if the layer is visible by default.

brightness

A brightness modifier to darken the layer. This is particularly useful for controlling translucent layers or to make certain layers stand out.

This property is scopes under an options object:


MapXyzSourceProvider Component

The MapXyzSourceProvider allows you to add an XYZ source URL that serves map tiles in the XYZ (a.k.a Slippy Map Tilenames) format.

url

The tile source URL to connect to. The varies based on different flavours of servers, but a sample that uses an API key for authentication would look like this:

https://maptiles.p.rapidapi.com/local/osm/v1/zoom/x/y.png?rapidapi-key=YOUR-KEY

MapEntityLayerProvider Component

The MapEntityLayerProvider represents a layer that may contain one or more entity (and entity kinds). You need to also pass in a MapEntityProvider as a child snippet to provide the necessary information for to render entities in this layer.

Examples:

MapEntityLayerProvider Component
<MapEntityLayerProvider layer_name="Aircraft" is_visible>
<MapEntityProvider kind={"aircraft"}>...</MapEntityProvider>
</MapEntityLayerProvider>

layer_name

The name of the layer, this is used by the MapLayerManager as a label for toggling the visibility.

is_visible

A boolean true or false value to determine if the layer is visible by default.


MapEntityProvider Component

The MapEntityProvider requires a snippet to be supplied for a chosen "kind".

In the following example, for all geoentities of kind "example", a floating, red <div> that shows the value of example_property gets rendered. You can supply any snippet you want, and you can reference the geoentity's properties via the snippet's parameters. The snippet will get rendered at the position of the entity.geojson.geometry.coordinates (this is for Point features).

MapEntityProvider Component (Point Geometry)
<MapEntityLayerProvider layer_name="Example Layer">
<MapEntityProvider kind={"example"}>
{#snippet children(entity: Gis.MapEntity<{example_property: string}>)}
{#if entity.geojson.type === "Feature"}
<div class="p-4 bg-red-500">{entity.geojson.properties.example_property}</div>
{/if}
{/snippet}
</MapEntityProvider>
</MapEntityLayerProvider>

For lines, polygons etc. (shapes more complex than points) "geom" information needs to be specified for the renderer.

MapEntityProvider Component (Polygon Geometry)
<MapEntityLayerProvider layer_name="Airport">
<MapEntityProvider
kind={"airport"}
geom_style={{
fill: "#999999",
"fill-opacity": 0.5,
stroke: "#FF0000",
"stroke-opacity": 1,
}}
on_geom_click={(map_entity: Gis.MapEntity<Record<string, unknown>>) => {
if (!map_entity.set_entity_style) {
return;
}

map_entity.set_entity_style({
fill: `rgb(${Math.floor(Math.random() * 255)} ${Math.floor(Math.random() * 255)} ${Math.floor(
Math.random() * 255
)})`,
"fill-opacity": 0.5,
stroke: "#FF0000",
"stroke-opacity": 1,
});
}}
on_geom_hover={(map_entity: Gis.MapEntity<Record<string, unknown>>) => {
if (!map_entity.set_entity_style) {
return;
}

map_entity.set_entity_style({
fill: `rgb(${Math.floor(Math.random() * 255)} ${Math.floor(Math.random() * 255)} ${Math.floor(
Math.random() * 255
)})`,
"fill-opacity": 0.5,
stroke: "#FF0000",
"stroke-opacity": 1,
});
}}
></MapEntityProvider>
</MapEntityLayerProvider>

MapLayerManager Component

The MapLayerManager is a prebuilt UI component that adds map layer controls as a UI overlay.

It expects to be passed a child snippet which will be used as the button to open the manager. The following example shows the child snippet with a <Button> component:

Example
<MapLayerManager>
<Button>Layers</Button>
</MapLayerManager>
MapLayerManager UI

MapBookmarkManager Component

The MapBookmarkManager is a prebuilt UI component that adds controls to add and delete bookmarks.

It expects to be passed a child snippet which will be used as the button to open the manager. It also expects an array of Gis.MapBookmarks to render on initialization.

The following example shows the child snippet with a <Button> component:

Example
<MapBookmarkManager {bookmarks}>
<Button>Bookmarks</Button>
</MapBookmarkManager>
MapBookmarkManager UI

MapControlPanel Component

The MapControlpanel provides a UI with a row of commonly used map functions, such as:

  • Zoom in
  • Zoom out
  • Layer Filter/Selection
  • Pointer tool
  • Polyline/Polygon drawing tool
  • Ellipse and Rectangle drawing tool
  • Geometry border style selector
  • Geometry border width
  • Geometry border colour swatch
  • Geometry fill colour swatch

You can use this component as reference for your own implementations, or use it as-is.

Map Functions

To access functions to control map behaviour, you can use the bindable Gis.GisMap object from the Map component.

You can then access the following functions:


Drawing modes are available via the set_mode_* functions - the functions put the map into different sub states that have specific predefined behaviour to allow you to easily draw geometries (e.g. going into pointer mode changes the click behaviour such that you can click on annotations to select them, or double-click on geometries to edit them):

set_mode_view

Call this function to put the map in view mode, which does not allow you to select annotations.

map.engine.set_mode_view();

set_mode_pointer

Call this function to put the map in pointer mode, which allows you to select annotations on single click, or go into edit mode on double-click.

map.engine.set_mode_pointer();

set_mode_draw_polyline

Call this function to put the map in line drawing mode, which allows you to draw lines - you can connect the end points of lines to form a polygon - which can have a fill colour.

map.engine.set_mode_draw_polyline();

set_mode_draw_rectangle

Call this function to put the map in rectangle drawing mode, which allows you to draw rectangles. You can hold shift down to keep the aspect ratio 1:1 (square) as you drag out your rectangle.

map.engine.set_mode_draw_rectangle();

set_mode_draw_ellipse

Call this function to put the map in ellipse drawing mode, which allows you to draw ellipses. You can hold shift down to keep the aspect ratio 1:1 (circle) as you drag our your ellipse.

map.engine.set_mode_draw_ellipse();

set_mode_edit_geometry

Call this function to edit a desired geometry by it's id. The list of geometries are available in a dictionary of GeoJSON features within map.draft_features. The keys are the id, and the values are the features.

// Note this 'value' is the key, not the feature
const example_feature_id = map.draft_features.keys().next().value;
map.engine.set_mode_edit_geometry(example_feature_id);

set_mode_select_geometry

Call this function to select a desired geometry by it's id. The list of geometries are available in a dictionary of GeoJSON features within map.draft_features. The keys are the id, and the values are the features.

// Note this 'value' is the key, not the feature
const example_feature_id = map.draft_features.keys().next().value;
map.engine.set_mode_select_geometry(example_feature_id);

The following functions (annotate_point, annotate_polyline, and annotate_polygon) can be passed an options object, which can contain:

  • id: A unique string to represent the geometry
  • geom_style: A Gis.GeomStyle to use to style the geometry
  • z_index: A number to control the layer order of the geometry

annotate_point

Call this this function to add a point annotation to the map - you must specify coordinates in the form of a 2-element array, containing longitude and latitude in WGS84 degrees respectively.

Note: This does not add the annotation to the map.engine.draft_features, the draft_features

const coordinates = [-9.3515381, 53.8371663];
map.engine.annotate_point(coordinates, options);

annotate_polyline

Call this function to add a polyline annotation to the map - you must specify coordinates in the form of a 2D array, similar to the GeoJSON LineString format.

const coordinates = [
[-9.351092, 53.838159],
[-9.351789, 53.837571],
[-9.351004, 53.837645],
[-9.351756, 53.837003],
];
map.engine.annotate_polyline(coordinates, options);

annotate_polygon

Call this function to add a polygon annotation to the map - you must specify coordinates in the form of a 2D array, similar to the GeoJSON LineString format, except the final coordinate is expected to be the same as the first, meaning it is like a GeoJSON Polygon geometry with one less dimension (the GeoJSON format has an extra dimension to allow the definition one of more polygon to represent a hole in it).

const coordinates = [
[-9.35154, 53.838099],
[-9.351335, 53.838104],
[-9.351304, 53.838014],
[-9.351041, 53.837952],
[-9.351005, 53.837844],
[-9.350895, 53.837688],
[-9.350977, 53.837502],
[-9.35154, 53.836949],
[-9.351833, 53.83692],
[-9.351949, 53.837589],
[-9.351854, 53.837728],
[-9.351665, 53.837883],
[-9.35154, 53.838099],
];

map.engine.annotate_polygon(coordinates, options);

delete_point_annotation

Delete a point annotation by it's id.

map.engine.delete_point_annotation("example_id");

delete_polyline_annotation

Delete a polyline annotation by it's id.

map.engine.delete_polyline_annotation("example_id");

delete_polygon_annotation

Delete a polygon annotation by it's id.

map.engine.delete_polygon_annotation("example_id");

set_camera_behaviour

This function allows you to control the camera's behaviour - particulary, if it is allowed to pan, rotate or zoom. This is particularly useful if you are creating features that require dragging (which overlaps with the panning).

const options = {
panEnabled: false,
zoomEnabled: false,
rotateEnabled: false,
};

map.engine.set_camera_behaviour(options);

get_camera_direction

This function lets you retrieve the direction of the camera.

const direction = map.engine.get_camera_direction();

console.log(direction); // [ 1.32472, 0.2432, 2.512 ]

See Direction for more information.

get_camera_zoom

This function lets you retrieve the zoom of the camera.

const zoom = map.engine.get_camera_zoom();

console.log(zoom); // 14

See Zoom for more information.

fly_to

This function performs a camera fly (pan) to a location - the exact behaviour will depend on engine implementations, so the option allowed for CesiumJS is only duration but other engine implementations might allow for more options such as an easing functions or more.

Pass a camera view in for the engine to know the final desired state of the camera after flying.

const cam_view = {
zoom: 12,
position: [-9.351538, 53.837166],
};

map.engine.fly_to(cam_view, { duration: 0.5 });

coordinates_to_screen

This function is useful when you need to retrieve the screen (X and Y) coordinates based on the longitude and latitude, for example, if you need to create a popup at a specific area on the screen, or perform other calculations.

map.engine.coordinates_to_screen([-9.351538, 53.837166]);

The following are callback functions that are called when the events occur, you can subscribe to the events by assigning a callback function to them - this function will be called when the specific event occurs.

on_geometry_saved

Called whenever a geometry is created, or updated (saved).

map.event_subscriptions.on_geometry_saved = (geometry) => {
logger.info({ geometry }, "You saved a geometry!");
};

on_geometry_selected

Called whenever a geometry is selected (either in pointer mode or programmatically).

map.event_subscriptions.on_geometry_selected = (geometry) => {
logger.info({ geometry }, "You selected a geometry!");
};

on_geometry_deleted

Called whenever a geometry is deleted (either via the UI or when deleted programmatically).

map.event_subscriptions.on_geometry_deleted = (geometry) => {
logger.info({ geometry }, "You deleted a geometry!");
};

Example Page

A full example page, complete with server functions in use is installed by default as an example at:

  • src/routes/(private)/aoh/gis/+page.server.ts
  • src/routes/(private)/aoh/gis/+page.svelte