Advanced usage
Integrating Kano
To avoid the burden of developing a completely new application for every mapping needs you might have, Kano provides you with the capabilities to be integrated in your web application as an <iframe/> like this:
This iframe offers an API so that you can dynamically control the behaviour and the content of Kano, as well as how the embedding application reacts in real-time to changes in Kano, a.k.a micro frontend. You can read more about the underlying concepts in this article.
The API is a subset of the internal Kano components and uses post-robot to
- select which is the target component
- event name =
mapfor 2D map andglobefor 3D globe
- event name =
- transform external method calls to internal calls using the following event payload
- the
commandproperty is the mixin method name (e.g.isLayerVisible) - the
argsproperty is the expected method arguments (e.g. a string, an object or an array when multiple arguments are required)
- the
- retrieve internal method call result externally
- event response
datais the method result object
- event response
- retrieve internal property externally
- the
propertyproperty is the internal property name (e.g.layers) - event response
datais the returned property value
- the
TIP
Event messaging using post-robot is always async because it relies on the postMessage API under-the-hood.
WARNING
In-memory data exchange is Json and more specifically GeoJson for map features. Do not try to inject functions or "complex" objects (e.g. class instances) in event payloads.
WARNING
You must use the same version of the post-robot library as the one used by Kano. For now, Kano relies on the 10.0.42 version of post-robot.
In addition to the commands used to access mixin methods there are a couple of dedicated commands listened by Kano to:
setLocalStorageset key/value pairs (provided as event data payload) in its local storage, typically useful to inject access tokenssetConfigurationset key/value pairs to override its default configuration, typically useful to configure application name, available components or actions
The following keys can be set in local storage to alter the application behaviour:
appName-jwtto skip the login screen by injecting an authentication tokenappName-welcomeasfalseto avoid displaying the welcome screen on first loginappName-installasfalseto avoid displaying the PWA installation screenappName-disconnect-dialogasfalseto avoid displaying the disconnection screen when server connection is lostappName-reconnect-dialogasfalseto avoid displaying the reconnection screen when server connection is restaured
Here is a simple code sample:
<script src="https://cdn.jsdelivr.net/npm/post-robot@10.0.10/dist/post-robot.min.js"></script>
<iframe id="kano" title="Kano" allow="geolocation *" style="width: 1024px; height: 768px;" src="kano.kalisio.com">
<script>
var kano = document.getElementById('kano').contentWindow
// Wait for Kano to be initialized
postRobot.on('kano-ready', function() {
// Optionnaly overrides default setup of Kano
postRobot.send(kano, 'setConfiguration', { 'appName': 'xxx' })
.then(function() {
// Optionnaly set a valid token to avoid authentication
return postRobot.send(kano, 'setLocalStorage', { 'xxx-jwt': 'yyy' })
})
.then(function() {
// Show and zoom to a layer
return postRobot.send(kano, 'map', { command: 'showLayer', args: 'Layer name' })
})
.then(function() {
return postRobot.send(kano, 'map', { command: 'zoomToLayer', args: 'Layer name' })
})
.then(function() {
return postRobot.send(kano, 'map', { property: 'layers' })
})
.then(function(result) {
console.log('Layer list', result.data)
})
})
</script>A full sample exploring the different ways to interact with the API is provided here. When running the demo you can dynamically call API methods when toggling the different buttons on the left.
WARNING
Depending on the configuration of your Kano instance some features might not work as expected in the sample as it relies on some specific layers to exist.
Listening to events
There are a lot of events to be listened by integrating application to be aware of Kano internal states or user behaviour.
WARNING
You should add a listener for each event in your application, even if you don't need to do any processing, otherwise the post-robot library will raise a warning.
Frontend events
The following ones are related to Kano states:
kano-readywhen the Kano application has been initialized in the iframe so that you can safely use the iframe APIapi-readywhen the Kano backend connection has been initialized in the iframe so that you can safely call the backend APIkano-loginwhen the user has been authenticated in the Kano applicationkano-logoutwhen the user has been unauthenticated in the Kano applicationkano-disconnectedwhen the Kano application has been disconnected from the websocketkano-reconnectedwhen the Kano application has been reconnected to the websocketmap-readywhen the 2D map component has been initialized in the Kano application so that you can safely use the underlying APImap-destroyedwhen the 2D map component has been destroyed in the Kano application before switching to another routeglobe-readywhen the 3D globe component has been initialized in the Kano application so that you can safely use the underlying APIglobe-destroyedwhen the 3D globe component has been destroyed in the Kano application before switching to another route
The following ones are related to layers management with the layer definition as layer payload property:
layer-addwhenever a new layer will be added to the 2D/3D maplayer-addedwhenever a new layer has been added to the 2D/3D map (from the internal catalog or externally)layer-removedwhenever a layer has been removed from the 2D/3D maplayer-shownwhenever a layer has been shown in the 2D/3D maplayer-hiddenwhenever a new layer has been hidden in the 2D/3D maplayer-updatewhenever a real-time GeoJson layer will be updated in the 2D/3D maplayer-updatedwhenever a real-time GeoJson layer has been updated in the 2D/3D map
The following ones are related to 2D panes management with the pane name as pane payload property:
pane-addedwhenever a new pane has been added to the 2D map (from the internal catalog or externally)pane-removedwhenever a pane has been removed from the 2D mappane-shownwhenever a pane has been shown in the 2D mappane-hiddenwhenever a new pane has been hidden in the 2D map
The layer-add and layer-update events are particular as it might expect a response, in this case the altered data will be taken into account instead of the original data when updating the layer:
postRobot.on('layer-add', (event) => {
const layer = event.data
if (layer.name === 'MyLayer') {
// Update the layer, eg change the data filter
Object.assign(layer.baseQuery, { 'properties.user': 'MyUser' })
return layer
}
})
postRobot.on('layer-update', (event) => {
const { name, geoJson } = event.data
if (name === 'MyLayer') {
const features = geoJson.features || [geoJson]
return {
type: 'FeatureCollection',
features: features.map(feature => {
// Update the features
...
})
}
}
})The following ones are related to user interaction (mouse or gesture):
clickwhenever map or a feature from a layer has been clicked (left button or tapped) in the 2D/3D map,dbclickwhenever map or a feature from a layer has been double-clicked (left button or tapped) in the 2D/3D map,contextmenuwhenever map or a feature from a layer has been right-clicked (or long tapped) in the 2D map,mousedownwhenever the user pushes the mouse button on the 2D map,mouseupwhenever the user releases the mouse button on the 2D map,mouseoverwhenever the mouse enters the map or a feature from a layer in the 2D map,mouseoutwhenever the mouse leaves the map or a feature from a layer in the 2D map,mousemovewhenever the mouse moves on the 2D map,touchstartwhenever a touch point is placed on the map or a feature from a layer in the 2D map,touchendwhenever a touch point is removed from the map or a feature from a layer in the 2D map,touchcancelwhenever a touch point has been disrupted in the 2D map,touchmovewhenever a touch point is moved in the 2D map.
Most user interaction events will provide you with the following properties as data payload:
longitudeandlatitudecoordinates of the interaction,featureandlayer(descriptor) when the target element is a feature from a layer,containerPointwithxandycoordinates of the point where the interaction occurred relative to the map сontainer,layerPointwithxandycoordinates of the point where the interaction occurred relative to the map layer.
TIP
For touch events the former properties at root level of the data payload are related to the first touch point (ie single-touch gesture). If you'd like to get information about all touch points for multi-touch gesture you will similarly get longitude, latitude, containerPoint and layerPoint values for all touch points in touches, changedTouches and targetTouches arrays relating to the same original touch event properties.
By default only layer-added, layer-shown, layer-hidden, layer-removed, pane-added, pane-shown, pane-hidden, pane-removed, click, dbclick and contextmenu events are sent and you should enable more (respectively disable), using the allowForwardEvents (respectively disallowForwardEvents) configuration option:
postRobot.send(kano, 'setConfiguration', {
// Allow more events to be emitted
'mapActivity.allowForwardEvents': ['mouseover', 'mouseout', 'mousemove', 'contextmenu']
// Do not receive these events
'mapActivity.disallowForwardEvents': ['mousemove']
})
// React to right-click (similar for others events like mouseover, mouseout, mousemove)
postRobot.on('contextmenu', (event) => {
const { latitude, longitude, feature, layer, containerPoint } = event.data
const { x, y } = containerPoint
if (feature) {
// The event targets a feature from a layer
} else {
// Otherwise the event targets the map background
}
})TIP
A feature can be tagged to stop events propagation, either immediate or not, by specifying it in style:
{
type: 'Feature',
…
style: { stopImmediatePropagation: ['mousedown', 'touchmove'] }
}This can be used to e.g. prevent the map to be dragged when touching a specific feature.
The following events are related to geometry editing:
dragstartwhenever the user starts dragging a 2D marker,dragendwhenever the user stops dragging a 2D marker,dragwhile the user drags a 2D marker,edit-startwhenever the user starts using the geometry editor,edit-stopwhenever the user ends using the geometry editor,edit-point-movedwhenever a point is edited using the geometry editor,
TIP
A point feature can be tagged as draggable by specifying it in style:
{
type: 'Feature',
…
style: { draggable: true }
}To drag line or polygon vertices you should use the geometry editor.
The following events are related to map state changes and do not provide additional properties like interaction events:
movestartwhenever the view of the 2D map starts changing (e.g. user starts dragging the map),moveendwhenever the center of the 2D map stops changing (e.g. user stopped dragging the map),moveduring any movement of the 2D map (including pan and fly animations),zoomstartwhenever the 2D map zoom is about to change (e.g. before zoom animation),zoomendwhenever the 2D map zoom changed (after any animations),zoomduring any change in zoom level, including zoom and fly animations,rotatewhenever the map bearing is changed (will provide an additionalbearingproperty as data payload).
TIP
When using animations with center() or updateLayer() a large number of map state change events can be sent, which can overflow the embedding application. Similarly, user interaction events during a drag or mouse move can be numerous. For this reason, these events are subject to a limitation and are sent no more than every configured number of milliseconds (i.e. throttle) in the activity configuration object eventsThrottle:
statethrottle value for map state change events,mousethrottle value for map mouse interaction events,touchthrottle value for map touch interaction events.
Backend events
Backend service events can be listened by integrating application, in this case the serviceEvent property, respectively the data property, contains the service event name, respectively service event data, in the post-robot event payload:
catalogwhenever a service event is emitted on thecatalogservicefeatureswhenever a service event is emitted on thefeaturesservice
For instance, you can listen to changes in the catalog service like this:
postRobot.on('catalog', (event) => {
const { serviceEvent, data } = event.data
console.log(`Received ${serviceEvent} catalog event`)
})Kano also provides you with an internal event bus service called events that can be used to dispatch custom events to all connected clients. This service internally has only a create method to send events, you can send a custom event named item-selected through this service like this:
// Tell others clients selection changed
await postRobot.send(kano, 'event', {
name: 'item-selected', data: { id: item.id }
})Others clients can listen to this custom event like this:
// Listen to selection change
postRobot.on('item-selected', (event) => {
const { id } = event.data
...
})Accessing the underlying API
You can access the backend API using either the Feathers client or raw HTTP REST requests. However, in order to ease integration you can also access the backend API through the iframe API. For this simply target the api component using post-robot, which transform external method calls to internal calls using the following event payload:
- the
serviceproperty is the target service name (e.g.catalog) - the
operationproperty is the target service operation name (amongget,find,update,patch,remove) - the
argsproperty is the expected service operation arguments
Event response data is the method result object. In addition to the event used to access service operations the api-ready event is to be listened by integrating application to know when the Kano backend API has been initialized in the iframe so that you can safely use it.
Here is a simple code sample:
<script src="https://cdn.jsdelivr.net/npm/post-robot@10.0.10/dist/post-robot.min.js"></script>
<iframe id="kano" title="Kano" allow="geolocation *" style="width: 1024px; height: 768px;" src="kano.kalisio.com">
<script>
var kano = document.getElementById('kano').contentWindow
// Wait for map to be initialized
postRobot.on('map-ready', () => {
// Request saved user contexts and activate the first one if any
postRobot.send(kano, 'api', { service: 'catalog', operation: 'find', args: [{ query: { type: 'Context' } }] })
.then((result) => {
const response = result.data
if (response.total > 0) postRobot.send(kano, 'map', { command: 'loadContext', args: response.data[0] })
})
})
</script>Client-side hooks
As you cannot directly access the underlying Feathers services from the iframe, the API allows you to setup events to be sent whenever a hook is run. Upon event reception you will get hook items as input, can alter it and send it back as output. This way the original hook items will be altered as usual in Kano.
await postRobot.send(kano, 'hooks', {
service: 'catalog',
// Emitted event names can be changed, by default it will look like eg 'catalog-after-find-hook'
hooks: { after: { find: { name: 'catalog-loaded' } } }
})postRobot.on('catalog-loaded', (event) => {
const { items } = event.data
// Update items
items.forEach(item => {
...
})
return items
})Developing in Kano
Kano is powered by the KDK and rely on its main abstractions. If you'd like to develop an application based on Kano or extend Kano we assume you are familiar with this technology. Indeed, Kano is based on the KDK and makes the best use of all the features offered by the provided cartographic components and services.
Add stickies
The most simple way to develop in Kano is to design and integrate your own components in the 2D or 3D activity. For this you simply have to
- Put you single-file component(s) in the
src/componentsfolder (e.g.MyComponent.vue) - Update the configuration to declare your component(s) in the 2D/3D activity by adding a
local.jsin theconfigfolder like this:
module.exports = {
mapActivity: { // Can also be globeActivity
stickies: {
content: [{
id: 'my-component', position: 'left', offset: [18, 0], content: [{ component: 'MyComponent' }]
}]
}
}
}Then build/run the application as usual.
The component file should look e.g. like this:
<template>
<div>
<q-dialog ref="myDialog">
</q-dialog>
<q-btn round color="primary" icon="edit_location" @click="showDialog">
</q-btn>
</div>
</template>
<script>
export default {
name: 'my-component',
inject: ['kMap'],
data () {
return {
...
},
methods: {
async showDialog () {
this.$refs.myDialog.show()
},
onTimeChanged () {
...
}
},
async mounted () {
// To be aware of time change
this.kMap.$on('current-time-changed', this.onTimeChanged)
...
},
beforeUnmount () {
this.kMap.$off('current-time-changed', this.onTimeChanged)
}
}
</script>
// Required translations in JSON format
<i18n>
{
"fr": {
...
}
},
"en": {
...
}
}
}
</i18n>It's also possible to create separated i18n files if you'd like, simply put your plugin_en.json, plugin_fr.json, etc. files in the src/i18n folder before building the app.
Add custom code
You can update the plugin.js entry point in the boot folder in order to insert your own features (functions, mixins, etc.). If you'd like to enhance the default activities provided by kano you can register your own mixins in this file by using the mixin store:
import { MixinStore } from '../mixin-store'
import myMixin from '../my-mixin.js'
MixinStore.set('my-mixin', myMixin)Then add it to the configuration of the target activity in order to make Kano apply it automatically:
module.exports = {
mapActivity: { // Can also be globeActivity
additionalMixins: [ 'my-mixin' ],
...
}
}