Documentation

6 Operating instructions

The section contains instructions for usage of various functionalities of Oskari.

6.1 Updating Oskari version

This section guides you through updating your Oskari instance.

We are updating the frontend and server versions at the same time and for the best compatibility it's best to use same version in both. Hotfix/patch versions might not always match but with version major.minor.patch the major and minor versions should match.

Before updating, the following links are recommended to at least skim through:

6.1.1 Migration and release notes

In most cases updating Oskari version means:

  1. updating the version of Oskari dependency for the frontend and server and
  2. building new versions of your frontend app/server

Sometimes there are additional things that need to be done when updating. These are documented on the Migration Guide in oskari-server repository.

There can be also application spesific things that need to be taken into account when updating. For example, your application code might not compile after updating if you have changed some class in oskari-server that you are using directly in your app.

You should also at least skim through the Release notes for both the server and the frontend to see if there is something that might affect your app. There might be, for example, a new implementation of a bundle/functionality that you want to use instead of the old one.

It's also a good idea to see any PRs/changes for the sample application to see if there are things that you can streamline/change in your application for the newer version:

Note! The idea is NOT to you use the sample-application/server directly but to use it as a template to create your own app/server. For example the database migrations in server-template assume they are run on an empty database and might not work properly if copied directly to your server code. We change the sample migrations between versions to make the most sense as simple examples for the latest version while actual apps (copies of sample-server) should work to migrate the existing database of the app.

6.1.2 Updating the server

Updating an Oskari-powered server that is based on the sample-server-extension template is done by updating the value of oskari.version property in pom.xml file:

<properties>
    <oskari.version>1.0.0</oskari.version>
    ...
</properties>

To the new version:

<properties>
    <oskari.version>1.0.1</oskari.version>
    ...
</properties>

After this run mvn clean install to generate a new oskari-map.war under {your.server.repository.root}/webapp-map/target. Replace the oskari-map.war with the new one as described on Build customized server-side application.

6.1.3 Updating the frontend

Updating an Oskari application that is based on the sample-application template is fairly simple as well. For the frontend the oskari-frontend (and possible oskari-frontend-contrib) dependency is updated by changing the version number in package.json file from the current version in the app:

"dependencies": {
  "oskari-frontend": "https://git@github.com/oskariorg/oskari-frontend.git#1.0.0"
},

To the new version:

"dependencies": {
  "oskari-frontend": "https://git@github.com/oskariorg/oskari-frontend.git#1.0.1"
},

When updating Oskari version, feel free to change the application version as well in package.json of your application to signal that the application has been updated.

After this you need to run npm install to install any new/changed libraries and npm run build to generate a new build under {your.server.repository.root}/dist/[version on package.json] as described on Build customized frontend application.

Finally, remember to update the newly built frontend version to oskari-ext.properties under oskari-server/lib:

oskari.client.version=dist/[version from your package.json]

6.2 Usage instructions

This documentation section contains general usage instructions on various parts of Oskari.

6.2.1 How to use Oskari filter

JSON filter object for filtering features by features' property keys and values.

Use cases

  • Define filter for optional styles
  • Define filter for WFS layer

Oskari filter definition:

{
    "property": {
        "key": "type", // feature's property name

        // Operators
        "value": "road" || 10000 || true // equal comparison
        "in": ["tarmac","sand", "gravel"], // for multiple possible values (OR)
        "notIn": ["sand", "gravel"], // for multiple possible NOT values (OR)
        "like": "tarm*", // for a pattern tarm*
        "notLike": "*tarm*", // for a NOT pattern *tarm*

        // Operators for numerical comparison
        "greaterThan": 60, // strictly greater than
        "atLeast": 60, // greater than or equal to
        "lessThan": 80, // strictly  less than
        "atMost": 80, // less than or equal to

        // Case sensivity for value, in, notIn, like and notLike operators
        "caseSensitive": false
    },
    "AND": [...], // to add multiple conditions with AND operator
    "OR": [...] // to add multiple conditions with OR operator
}

Property filter should have key and one operator. Key defines which feature's property and operator which comparison operator are used for filttering. For range filters two operators can be used in same property filter. If additional definitions are given then filter doesn't work as intended.

Filtered WFS layer

WFS layer filter is stored in attributes field. If layer has filter then GetFeature requests are made with bbox and specified property filter.

WFS layer filter examples

To have WFS layer that contains only features which have regionCode property value '091'. CQL: regionCode = '091'

{
    "filter": {
        "property": {
            "key": "regionCode",
            "value": "091"
        }
    }
}

To have WFS layer that contains only features which have population between 10000 and 200000 (begin and end values are excluded). CQL: population > 10000 and population < 200000

{
    "filter": {
        "property": {
            "key": "population",
            "greaterThan": 10000,
            "lessThan": 200000
        }
    }
}

To have WFS layer that contains only features which regionCode '091' and serviceType is 'school', 'pre-school' or 'high-school'. CQL: regionCode = '091' and (serviceType = 'school' or serviceType = 'pre-school' or serviceType = 'high-school')

{
    "filter": {
        "AND": [{
            "key": "regionCode",
            "value": "091"
        }, {
            "key": "serviceType",
            "in": ["school", "pre-school", "high-school"]
        }]
    }
}

Oskari style

Using filter with optional styles see: Oskari style

6.2.2 How to use Oskari style

JSON style object for styling layers. VisualizationForm component also supports getting and setting style values in Oskari style format.

Use cases

  • Define styling for vector layers handled by WfsVectorLayerPlugin
  • Define styling for feature layers
  • VisualizationForm

Oskari style definition

All of the fields and objects defined here are optional in the Oskari style JSON. Anything can be omitted from the example below.

{
    "fill": { // fill styles
        "color": "#FAEBD7", // fill color
        "area": {
            "pattern": -1 // fill style
        }
    },
    "stroke": { // stroke styles
        "color": "#000000", // stroke color
        "width": 1, // stroke width
        "lineDash": "solid", // line dash, supported: dash, dashdot, dot, longdash, longdashdot and solid
        "lineCap": "round", // line cap, supported: butt, round and square
        "lineJoin": "round" // line corner, supported: bevel, round and miter
        "area": {
            "color": "#000000", // area stroke color
            "width": 1, // area stroke width
            "lineDash": "dot", // area line dash
            "lineJoin": "round" // area line corner, supported: bevel, round and miter
        }
    },
    "text": { // text style
        "fill": { // text fill style
            "color": "#000000" // fill color
        },
        "stroke": { // text stroke style
            "color": "#ffffff", // stroke color
            "width": 1 // stroke width
        },
        "font": "bold 12px Arial", // font
        "textAlign": "top", // text align
        "offsetX": 12, // text offset x
        "offsetY": 12, // text offset y
        "labelText": "example", // label text
        "labelProperty": "propertyName" // read label from feature property
    },
    "image": { // image style
        "shape": 5, // 0-6 for default markers. svg or external icon path
        "size": 3, // Oskari icon size.
        "sizePx": 20, // Exact icon px size. Used if 'size' not defined.
        "offsetX": 0, // image offset x
        "offsetY": 0, // image offset y
        "opacity": 0.7, // image opacity
        "radius": 2, // image radius
        "fill": {
            "color": "#ff00ff" // image fill color
        }
    },
    "inherit": false, // For hover. Set true if you wan't to extend original feature style.
    "effect": "auto normal" // Requires inherit: true. Lightens or darkens original fill color. Values [darken, lighten, auto] and [minor, normal, major].
}

Vector layer styling

Vector layer styling is stored in options field. Feature style overrides default style definitions for all features. Optional styles are used for specific features only which overrids default and feature style definitions. One named style has only one feature style and may have more than one optional styles.

{
    "styles": {
        "Custom MVT style": {
            "featureStyle": {...},
            "optionalStyles": [{...}]
        }
    }
...
}

WFS layer styling example

WFS layer options field:

{
    "styles":{
        "Red lines": {
            "featureStyle": {
                "stroke": {
                    "color": "#ff0000"
                }
            }
        }
    }
}

Hover style

Hover options describes how to visualize layer's features on hover and what kind of tooltip should be shown. Note that features isn't hovered while drawing is active (DrawTools).

{
    featureStyle: {
        inherit: true,
        effect: 'darken',
        // Oskari style definitions
        fill,
        stroke,
        image,
        text
    },
    // Tooltips content as an array. Each object creates a row to the tooltip.
    content: [
        // "key" is a label and will be rendered as is.
        { key: 'Feature Data' },
        // "valueProperty" and "keyProperty" are fetched from the feature's properties.
        { key: 'Feature ID', valueProperty: 'id' },
        { keyProperty: 'name', valueProperty: 'value' }
    ]
}

Optional styles

The styling definitions for optional style is same as above. Features that uses optional style are filtered with Oskari filter definition by features' property keys and values. To filter features by different property keys you can use AND or OR operators.

You can pass the optional styles array with the key optionalStyles when you pass the base style with key featureStyle in for example AddFeaturesToMapRequest.

Optional style examples

To define style which is affected to featuers which regionCode is '091'.

[{
    "property": {
        "key": "regionCode",
        "value": "091"
    },
    "fill": {
        "color": "#ff0000"
    }
}]

Filtering features with different property keys use AND and OR operators. To define style which is affected to features which type is 'city' or 'town' and population is greater than 10000.

[{
    "AND": [{
        "key": "type",
        "in": ["city", "town"]
    }, {
        "key": "population",
        "greaterThan": 10000
    }],
    "image": {
        "shape": 5,
        "size": 4,
        "fill": {
            "color": "#3333ff"
        }
    },
    "text": {
        "labelProperty": "name"
    }
}]

To define style which is affected to features which surface property is 'tarmac' (case insensitive) or type is '*road*' or speedLimit is between 50 and 80 (begin and end values are included).

[{
    "OR": [{
        "key": "surface",
        "value": "tarmac",
        "caseSensitive": false
    }, {
        "key": "type",
        "like": "road"
    }, {
        "key": "speedLimit",
        "atLeast": 60,
        "atMost": 100

    }],
    "stroke": {
        "color": "#ff0000",
        "width": 3
    }
}]

6.2.3 How to add a bundle

Bundles can be considered as building blocks in an Oskari application. A bundle is a selection of JavaScript files that provide some functionality to be used as part of an application. A bundle can offer multiple implementations for a functionality which can then be divided into smaller packages for different application setups. For more about bundles see the documentation under Frontend framework.

In order to get bundle up and running in your Oskari-based application, the bundle needs to be registered to the database and added to an appsetup you want to show it on. There are two tables where it shoud be added:

  • oskari_bundle
  • oskari_appsetup_bundles

oskari_bundle includes rows for all available bundles in the Oskari instance. Any new bundles should be added/registered here so they can be used in an appsetup. It is recommended to use Flyway-scripts when making changes to database. Documentation about migrations can be found here.

Registering a new bundle

If you are adding a new bundle for your application or to the built-in ones under oskari-frontend, you should add a migration script to register the bundle on the database so the server recognizes it and allows using it on appsetups. The oskari-server provides org.oskari.helpers.BundleHelper helper class for doing this and a bundle can be registered with a migration like this (Replace <bundle-identifier> with the bundle id and fix the migration version in the class name):

package flyway.app;

import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
import org.oskari.helpers.BundleHelper;

public class VX_Y_Z__register_bundle extends BaseJavaMigration {

    @Override
    public void migrate(Context context) throws Exception {
        BundleHelper.registerBundle(context.getConnection(), "{{bundle-identifier}}");
    }
}

The Flyway migration checks that a bundle with the same id hasn't been registered before registering it. Note that the package of the migration (app here) dictates the Flyway module it is included in. In oskari-server most migrations are done on the oskari module, but if you have an application specific bundle you should add it in your applications migration module. As an SQL the same can be accomplished like this, but you would need to be sure you are not adding duplicates to the table:

-- Register bundle to oskari_bundle table
INSERT
INTO oskari_bundle
(
    name,
    config,
    state
)
VALUES
(
    '<bundle-identifier>',
    '{}',
    '{}'
);

If you want the bundle to have a default configuration or state anytime it's added to an appsetup, you can define them when registering the bundle in oskari_bundle table. You can also do this with the BundleHelper class.

Adding bundle to an appsetup

After bundle has been registered to oskari_bundle table, it can be added to oskari_appsetup_bundles table to be used in appsetups. The oskari-server provides org.oskari.helpers.AppSetupHelper helper class for doing this easily:

package flyway.app;

import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
import org.oskari.helpers.AppSetupHelper;

public class VX_Y_Z__add_bundle_to_apps extends BaseJavaMigration {

    public void migrate(Context context) throws Exception {
        AppSetupHelper.addBundleToApps(context.getConnection(), "{{bundle-identifier}}");
    }
}

By default this adds the bundle to all appsetups of type DEFAULT (default geoportal apps) and USER (geoportal based appsetups saved by users). There are also other useful methods on AppSetupHelper from checking if a bundle exists on an appsetup to updating a bundles state or config on an existing appsetup.

Below is an example SQL for adding a bundle to oskari_appsetup_bundles table manually:

-- Add bundle to default geoportal appsetup
INSERT
INTO oskari_appsetup_bundles
(
    appsetup_id,
    bundle_id,
    seqno,
    config,
    state,
    bundleinstance
)
VALUES (
    (SELECT id FROM oskari_appsetup WHERE application='geoportal' AND type='DEFAULT'),
    (SELECT id FROM oskari_bundle WHERE name='<bundle-identifier>'),
    (SELECT max(seqno)+1 FROM oskari_appsetup_bundles WHERE appsetup_id=(SELECT id FROM oskari_appsetup WHERE application='geoportal' AND type='DEFAULT')),
    (SELECT config FROM oskari_bundle WHERE name='<bundle-identifier>'),
    (SELECT state FROM oskari_bundle WHERE name='<bundle-identifier>'),
    '<bundle-identifier>'
);

After these steps, and when bundle is defined correctly in front-end code, the bundle should be loaded when starting your map application. Great way to check if the bundle is loaded at start is to look at startupSequence in GetAppSetup in developer console. The developer console will also warn if the server response references a bundle to be started as part of the application, but the javascript files for that bundle has not been included in the frontend application.

6.3 Other instructions

This operating instructions section contains this and that.