9 Configuration instructions
This section contains information on how to configure different functionalities of Oskari.
9.1 Customizing service logo
The logo of the service is by default displayed on the:
- LogoPlugin (lower left corner on the map by default)
- On printouts when the print functionality is enabled
- Browser tab/favicon
You can customize these on the server by adding binary images on the server classpath (or package them inside the war-file) and referencing them on the oskari-ext.properties
configuration file:
# These are the built-in logos for Oskari
# You can override them by changing the path to point to a png on the server classpath (for example resources under jetty)
logo.path.print=/org/oskari/util/logo_print.png
logo.path=/org/oskari/util/logo.png
# Or if they are under jetty/resources/images
# logo.path.print=/images/logo_print.png
# logo.path=/images/logo.png
Optionally you can also overwrite the image from LogoPlugin with CSS (for example on the overwritten.css file when having a my_logo.png on the same folder) with:
.logoplugin .logo {
content: url('my_logo.png');
}
If the logo is not square you might need this selector for set the size of the container for it, but otherwise the server configuration is the preferred method.
9.2 Custom favicon
Easiest way to override the browser tab icon (favicon.ico) is to replace the file under webapp-map/src/main/resources/favicon.ico
on your server repository. You can configure another location with oskari-ext.properties
, but the file name needs to match favicon.ico
regardless:
# under jetty-resources (when working folder is jetty-server folder)
# favicon.path=file:resources/img/favicon.ico
# with absolute path on the server
# favicon.path=file:/opt/public/img/favicon.ico
# reference file inside oskari-map.war
# favicon.path=classpath:images/favicon.ico
9.3 Customizing index map icon
The icon is displayed on published maps on the button that toggles the index map.
You can overwrite the images for dark and light themes with CSS (for example on the overwritten.css file when indexmap_icon_dark.png and indexmap_icon_light.png are on the same folder) with:
div.mapplugin.indexmap .indexmapToggle[class$=-dark] div.icon {
background-image: url('indexmap_icon_light.png');
}
div.mapplugin.indexmap .indexmapToggle[class$=-light] div.icon {
background-image: url('indexmap_icon_dark.png');
}
If the image's sides are not even or it is too large, you might also need the following style:
background-size: contain;
9.4 How to modify app setups
Oskari applications are generated by selecting a combination of functionalities (bundles
). These combinations are called application setups (appsetup
). You can think of them as a shopping list or feature toggles for items you want your application to have. As Oskari was born from a geoportal platform and is mostly used as a solution for web-mapping software the one thing most applications have on their shopping list is the map-functionality (mapfull
bundle). Other features that you can include in your application include features like search, layer listing, thematic maps functionalities and of course you can implement and include application specific features for your application.
9.4.1 Frontend
When building a customized application the main.js
on the customized app determines what functionalities can be used with that app (what code is packaged to be used in the app). When starting the app the frontend usually makes a GetAppSetup
XHR-request to the server asking what functionalities will be used on that app and for that user. The response is then constructed by the server based on what the uuid
parameter was in the request (this matches the uuid
column on oskari_appsetup
database table) and who the user is or what roles the user has (guest
, user
, admin
etc). The UUID parameter has a default and is optional but it is used to construct a listing of bundles that are linked to that application on the database table oskari_appsetup_bundles
.
After the frontend receives the response for GetAppSetup
it will try to start the bundles referenced in the response with the configuration and initial state received as part of the response. If the server response references a bundle that is NOT packed in the applications main.js the developer console will show a warning about this. Also worth noting is that everything referenced in main.js
might not be used by the application. In this case it's best to use the lazy-loader
option when referencing bundles in main.js
so bundles that are not used with all users don't need to be loaded by users that don't see them (like the admin bundles that are only used by users with the admin role).
9.4.2 Database
On the database these application setups are saved on the oskari_appsetup
table. The setups can be name
d and they have a type
to separate between the default geoportal application, an embedded map application (users can create these with the publisher functionality bundle), applications that the users have saved as their own personalized starting "views" etc. The setups reference the frontend application code with the application
column and the HTML-template (JSP-page) with the page
column.
The selection of functionalities for applications are stored on the database table oskari_appsetup_bundles
. A row on the table is a bundle in the application with possible configuration and state information. For example most applications have the map as functionality. You can see that the mapfull
bundle is repeated on the table multiple times with the appsetup_id
column pointing to the application where the map is used, state
has information like what are the coordinates and zoom level the map is initially set and what map layers are on the map when the application is shown. The config
column holds configuration like what feature toggles are activated for the user on that bundle. For the mapfull
bundle the config are toggles from supporting wms-layers (usually always enabled) to supporting adding markers on the map and enabling adding vector features to the map programmatically. The difference between state and config is that state usually changes during runtime on the client (user moves the map and coordinates are changed) and config doesn't (the user either has the ability use a feature or not). Both config and state options are described for bundles on the API documentation. The seqno
column is used to set the order in which the functionalities are started. Map is usually near the start of the sequence with a low value in seqno
. The bundle_id
column references the oskari_bundle
database table where name
is the "bundle id" that is recognized by the frontend.
9.4.3 Role based functionalities
The user roles can be used to inject more bundles for the frontend to start on top of the ones that match the application based on the database. This configuration is done in the oskari-ext.properties
file on the server. It is usually used to inject administrative functionalities (bundle
s) for users with the admin role, but it can be used for other role based injection as well.
# bundles that are added on runtime to view if user has one of configured role
actionhandler.GetAppSetup.dynamic.bundles = admin-layereditor, admin-users
# Linking dynamic bundles based on user roles
# Properties are named 'actionhandler.GetAppSetup.dynamic.[BUNDLE ID].roles'
# with value as comma-separated list of role names that should be served the bundle
actionhandler.GetAppSetup.dynamic.bundle.admin-layereditor.roles = Admin
actionhandler.GetAppSetup.dynamic.bundle.admin-users.roles = Admin
9.4.4 Oskari instance initial applications
The initial applications can be described as JSON-files on the server code and inserted to the database with Flyway-migrations. You can see these on the sample-server-extension that you can use as a template to build your own Oskari instance.
More information about Flyway and migrations:
The application specific migrations can of course be done however you want but this is a built-in option to use for migrating the database and can be used to modify the content on the database like adding or removing bundles from oskari_appsetup_bundles
. Notice that these migrations are only run once so when it has been run, modifying the code doesn't get the migration run again. You will need to delete rows on the oskari_status_[your apps module] to the migrations to run again on a given database.
9.4.5 Modifying applications after the initial database population
Here's an example Flyway-migration that you can add on your server-extension code. Note that the order in which migrations are run is determined by the version number on the filename (the below would be 3.0.3). When you want to add a new migration after one has been run, you need to bump the version to a higher number like 3.0.4, 3.1.0, 4.0.0. So take a note a consider a versioning strategy for your Oskari instance. For best practices the version on the frontend codes package.json, the version on the server Maven modules pom.xml and the version on the Flyway migration should be tied to each other someway. Also note that the package reads [your apps module]
. This is example
on our sample-server-extension but can and should be changed to something that describes your app. (Not sure why we didn't call it "app" instead of "example" as that would have been more generic to use without renaming/might change this later). Note that you can also have multiple Flyway-modules on your app. The modules that are used when the server runs is set on oskari-ext.properties
as db.additional.modules
.
The migration name is after two underscores after the version. This can be anything you like on your application but it's easier to maintain if naming makes sense with the migration that is being run.
package flyway.[your apps module];
import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
import org.oskari.helpers.AppSetupHelper;
import org.oskari.helpers.BundleHelper;
import java.sql.Connection;
/**
* Adds a bundle to all default/user appsetups
*/
public class V3_0_3__add_mybundle_to_apps extends BaseJavaMigration {
public void migrate(Context context) throws Exception {
Connection connection = context.getConnection();
// id for the bundle to be added
String bundleID = "mybundle";
// register a new bundle (for example application specific bundle to the database "oskari_bundle" table)
// this can be skipped when adding a "built-in" functionality to apps
BundleHelper.registerBundle(connection, bundleID);
// add the bundle to
AppSetupHelper.addBundleToApps(connection, bundleID);
}
}
For removing bundles after they have been added you can see the other methods available on the AppSetupHelper class. You also don't need to use these helpers and can modify the tables with for example SQL. These are just common operations so we have helpers for them to make it as easy as it can be.
9.5 How to configure 3D map view
Oskari supports 3D map view since version 1.54 as an optional 3D-capable mapmodule has been added to Oskari. 3D mapmodule is based on Ol-Cesium library, which is meant for integrating OpenLayers and Cesium. Since Oskari 2D uses OpenLayers for map rendering and many map tools are built using OpenLayers most of the tools work out-of-the-box in 3D with Ol-Cesium.
Oskari supports 3D Tiles for 3D data rendering. 3D Tiles is an OGC Community standard and is designed for streaming and rendering massive 3D geospatial content.
Example 3D application can be seen on demo.oskari.org.
9.5.1 3D map view setup frontend
Oskari Sample application has example 3D application (geoportal-3d) and publishing template for embedded 3D maps (embedded-3d).
The changes from a usual 2D geoportal are listed below:
Add Ol-Cesium packages to main.js
Mandatory requirements is to add Ol-Cesium based mapmodule and layerplugin for 3D Tiles.
// Remove the default mapmodule import if you have one ('oskari-loader!oskari-frontend/packages/mapping/ol/mapmodule/bundle.js')
import 'oskari-loader!oskari-frontend/packages/mapping/olcs/mapmodule/bundle.js';
import 'oskari-loader!oskari-frontend/packages/mapping/olcs/map3dtiles/bundle.js';
Oskari frontend contains additional bundles for controlling camera and displayed time.
import 'oskari-loader!oskari-frontend/packages/mapping/time-control-3d/bundle.js';
import 'oskari-loader!oskari-frontend/packages/mapping/camera-controls-3d/bundle.js';
Cesium adjusts the sun's position and visualizes the shadows according to the time set for the map. You can also adjust displayed time with SetTimeRequest.
Using 2D and 3D maps side by side
You can have different appsetups for 2D and 3D maps like in https://demo.oskari.org/. Oskari frontend contains bundle for changing appsetups which adds 2D or 3D button to toolbar for switchint between the appsetups.
import 'oskari-loader!oskari-frontend/packages/mapping/dimension-change/bundle.js';
Note! You need to import 3D tiles layerplugin also with 2D geoportal application to support layer listing.
9.5.2 D map view setup backend
Oskari Sample server-extension has example Flyway module example3d to set backend ready for 3D. Oskari Jetty bundle's sample configuration doesn't have 3D enabled by default. You should add example3d
to db.additional.modules
.
Example view configuration geoportal-3d.json.
Add 3D tiles plugin to mapfull bundle's plugins array
{ "id": "Oskari.mapframework.mapmodule.Tiles3DLayerPlugin" }
Setting camera and time state
Optional camera settings and displayed time can be given in mapfull's
bundle state.
Configuring the terrain
Optional terrain configuration can be given in mapfull's
bundle configuration under mapOptions
.
"mapOptions": {
"terrain": {
"providerUrl": "{{url}}",
"ionAssetId": "{{asset-id}}",
"ionAccessToken": "{{your-ion-access-token}}"
}
}
terrain
and all it's children are optional.
- Using
providerUrl
one can set the TMS service end point. Supported terrain formats are heightmap-1.0 and quantized-mesh-1.0. - When
ionAssetId
is provided, the terrain is streamed from Cesium ion service which also requiresionAccessToken
. - When
ionAccessToken
is provided withoutionAssetId
Cesium World Terrain is used for providing detailed terrain all over the globe.
Configuration can be set also in oskari-ext.properties which overrides mapfull
's terrain configuration:
oskari.map.terrain.url=
oskari.map.terrain.token=
oskari.map.terrain.asset=
Configuring dimension change
Dimension change bundle loads page with different appsetup. Changing between 2D and 3D appsetups bundle should have wanted view's uuid in the configuration.
{"uuid": uuidToLoad}
Enable 3D map publishing
Embedded 3D maps needs own template which have to be set to 3D view. The uuid of the appsetup for 3D-publish template needs to be configured to the 3D geoportal appsetup metadata.
9.5.3 Adding your own 3D data
3D tilesets can be added to the map using admin tools and choosing 3D Tiles as the layer type. Example application http://demo.oskari.org has 3D model and buildings with textures layers from City of Helsinki.
9.5.4 Configuring 3D-tiles map layers
The 3D-tiles maplayers are saved in the database with all other datasets/services used for map layers in Oskari. The default fill color for 3D objects like buildings is a brownish tint/color in Oskari. This might make a textured surface look "dirty" by default but you can configure this. Also a tileset including a mesh-type scanned 3D environment has pretty low resolution with default settings in Cesium JS. Both of these can be tuned with database configuration in oskari_maplayer table by configuring the options column:
UPDATE oskari_maplayer
SET options='{
"maximumScreenSpaceError": 0,
"styles": {
"buildings": {
"fill": {
"color":"#FFFFFF"
}
}
}
}'
where id = [layer id for 3d tileset to configure];
The maximumScreenSpaceError defaults at value 16 in Cesium and affects the mesh type tileset resolution heavily.
The styles value is similar to oskari style JSON but the styling isn't fully supported yet for 3D-tiles. The buildings
part of the snippet above is the style name and you can choose it yourself. To make textured objects look better you might want to set the fill color to white.
9.5.5 Oskari 3D current state
The released version of 3D map is the initial version which can be developed further based on requirements that will emerge from use cases. The following features have been developed to support the use of 3D map:
- adding 3D Tiles datasets
- 3D map publishing
- support for WFS map layers and feature data in 3D map view
- camera flights with RPC API
- measure tools, coordinate tool, markers and own places extended to work also in 3D map view
- support for adding tilesets from Cesium ion platform with admin tool
9.5.6 Creating your own 3D datasets using Cesium Ion
Cesium ion is a platform for 3D geospatial data tiling, hosting and streaming. It converts different types of data (e.g. point clouds, 3D buildings, photogrammetry) to 3D Tiles format. A free Community account can be used for non-commercial personal projects, exploratory development, or unfunded educational activities within the defined usage limits. See the pricing page for more information.
Run in console
var mapmodule = Oskari.getSandbox().getStatefulComponents().mapfull.mapmodule;
mapmodule.getCesiumScene().primitives.add(
new Cesium.Cesium3DTileset({
url: Cesium.IonResource.fromAssetId(yourAssetId, {
accessToken: 'your-access-token-here'
})
})
);
9.6 How to configure a map projection
In Oskari application it is possible to configure different map projection for each view/appsetup. The map projection is configured for the mapfull bundle. Bundle configurations are in JSON-format that can be found in the database table portti_view_bundle_seq in column config where bundle_id matches the bundles id in portti_bundle table. If there are many views in the database, there are also many mapfull configurations.
To get all the configurations for the "mapfull" bundle from database run this script:
SELECT view_id, config FROM portti_view_bundle_seq WHERE bundle_id IN (SELECT id FROM portti_bundle WHERE name='mapfull')
Select the view_id that matches the view you want to configure (default view/publish template probably) and get the 'config' for that row. The default views are configured in oskari-ext.properties or if there is no configuration, the publish template is a view with type 'PUBLISH' and the default view with type 'DEFAULT'. Notice that if you have an old database with personalized default views you will also need to update all views. For this we recommend doing a Flyway-script.
oskari-ext.properties under {jetty.home}/resources:
# this is the publish template id
view.template.publish=3
# this is the generic default view id
view.default=4
# These are role specific default views
view.default.Admin=4
view.default.Guest=1
view.default.User=3
# This is the order in which user roles are matched to default views.
view.default.roles=Admin, User, Guest
SQL to get default view and publish template if not configured in oskari-ext.properties:
SELECT * from portti_view where type IN ('DEFAULT', 'PUBLISH');
The config includes functional configuration for the bundle which also includes for example which features to activate (plugins). The interesting part for projection configuration is the mapOptions and projectionDefs JSON-segments. These might not be present in the config in which case Oskari uses these defaults:
{
"mapOptions": {
"maxExtent": {
"left": 0,
"bottom": 0,
"right": 10000000,
"top": 10000000
},
"srsName": "EPSG:3067",
"resolutions":[2000, 1000, 500, 200, 100, 50, 20, 10, 4, 2, 1, 0.5, 0.25],
"units": "m"
},
"projectionDefs": {
"EPSG:3067": "+proj=utm +zone=35 +ellps=GRS80 +units=m +no_defs",
"EPSG:4326": "+title=WGS 84 +proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"
}
... there's propably more than these in the config, but the above are projection related ...
}
To configure the projection you must add or edit these JSON-segments. An example fragment for EPSG:3057 based map:
{
plugins : [ ....],
...,
"mapOptions": {
"maxExtent": {
"left": -39925.6142,
"bottom": -38980.4932,
"right": 1001273.8754,
"top": 986514.2978
},
"srsName": "EPSG:3057",
"resolutions":[2048,1024,512,256,128,64,32,16,8,4,2,1,0.5,0.25]
},
"projectionDefs": {
"EPSG:4326": "+title=WGS 84 +proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs",
"EPSG:3057": "+proj=lcc +lat_1=64.25 +lat_2=65.75 +lat_0=65 +lon_0=-19 +x_0=500000 +y_0=500000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs"
}
}
mapOptions
- srsName is the name of the projection that should be used
- maxExtent means projected bounds of the chosen projection. These are the bounds that the map engine will allow the user to pan the map. The bounds can be found f. e. in spatial reference web site.
- resolutions are the zoom levels
projectionDefs should include proj4js configurations for all the projections that are going to be used in the application. The oskari-server will try to detect the projections that are used and inject the projectionDefs automatically. The projectionDefs are stored in this file: https://github.com/oskariorg/oskari-server/blob/master/control-base/src/main/resources/fi/nls/oskari/control/view/modifier/bundle/epsg_proj4_formats.json. If the projection you want to use isn't available in the file you can insert the definition manually to the database config or make a pull request/issue to oskari-server to add the missing projection to the epsg_proj4_formats.json file.
The easiest way to change only the projection is to query the existing config from the database, modify the above projection specific configs and update the modified version back to the database. The projection can be configured to the database with the following kind of SQL. Change the configuration and the view_id to match your application!
UPDATE portti_view_bundle_seq
set config = '{ ... modified config ...}'
WHERE
bundle_id = (SELECT id FROM portti_bundle WHERE name = 'mapfull')
AND view_id = [the view id you want to update]
9.7 How to configure properties
Most settings can and should be overridden to match your environment by including an oskari-ext.properties
in the server classpath for example in Jetty you can add the custom properties as {JETTY_HOME}/resources/oskari-ext.properties
.
9.7.1 Properties
The default values for properties used in the map application is found in oskari-server/servlet-map/src/main/resources/oskari.properties
. To override all or any of these you can add an oskari-ext.properties
file in your classpath.
Any existing property will be overridden and any new ones will be available through PropertyUtil
methods in the application (for example PropertyUtil.get("propertyName")
).
Examples:
# set to true to get database populated with initial demo content
oskari.init.db=false
# true all ssl certs/hosts for debugging! configure certs on the server for production
oskari.trustAllCerts=true
oskari.trustAllHosts=true
# url path to call for ajax requests/action routes
oskari.ajax.url.prefix=/action?
# Supported locales, comma separated and default first
oskari.locales=en_US,fi_FI,sv_SE
The properties also setup various url links for search service, CSW metadata, etc that needs to be modified to match the server environment.
9.7.2 Environment variables
Environment variables can also be used to append or override any property set in properties files. Any set environment variable will take precedence over a property in properties files following the two rules:
- To avoid issues with casing and dots with environment variables all environment variables have to be uppercased and use underscores (
_
) instead of dots (.
) - To avoid conflicts with environment variables meant for other processes Oskari expects all environment variables to be prefixed with
OSKARI_
Examples:
# oskari-ext.properties
db.url=jdbc:postgresql://localhost:5432/oskaridb
db.username=oskari
db.password=oskari
# Using environment variables to override the configuration (bash example)
export OSKARI_DB_URL=jdbc:postgresql://localhost:2345/oskaridb
export OSKARI_DB_USERNAME=oskari2
export OSKARI_DB_PASSWORD=my-password
9.8 How to configure the search
The search functionality in Oskari can be extended, customized and configured in a number of ways. Different datasources can be exposed as search channels in Oskari. Search channels can provide textual search, reverse geocoding or both and can have other parameters that are passed to the datasource. Search channel access can be restricted by permissions and the queried channels (datasources) can be specified when making a search. By default all the available search channels are used for making searches, but this can be configured and customized.
- Guide for creating custom search channel
- Guide for creating ChannelProviders
- Guide for setting up WFS-based search channels
9.8.1 Configuring default search channels
Default channels are used for searching when no channel has been specified for the search. Search channels can specify if they should be included to be used in such queries. For channels extending and not overriding the SearchChannel.isDefaultChannel()
method this can be configured with oskari-ext.properties (CHANNEL_ID
is the annotated name of the channel like MyChannel
):
search.channel.CHANNEL_ID.isDefault=false
There's also action route specific configurations for GetSearchResult
(textual search) and GetReverseGeocodingResult
(reverse geocoding) for default channels to use:
actionhandler.GetSearchResult.channels=[comma-separated list of channel ids]
actionhandler.GetReverseGeocodingResult.channels=[comma-separated list of channel ids]
These will setup searches made using these action routes to use the configured channels and as channels are specified any default channel settings are not used.
9.8.2 Whitelisting enabled search channels
If you want to whitelist search channels and use only relevant ones for your application you can define a list of channel id's in oskari-ext.properties:
search.channels=OPENSTREETMAP_CHANNEL,MyChannel
Channels generated by ChannelProvider components are not restricted by this setting. Provider channels are always added. Read more about ChannelProviders.
9.8.3 Things to improve (TODO)
- Parallel search
- Maybe add result SRS and have a common transformation so channels don't need to care about it
- Logic for result ordering
- Describe result items in more detail and make them more generic to support more properties
9.9 Creating a custom location search channel
All search channels need to implement the SearchableChannel
interface from the
file service-search/src/main/java/fi/nls/oskari/search/channel/SearchableChannel.java
. The easiest way to add a
custom search channel is to extend the class service-search/src/main/java/fi/nls/oskari/search/channel/SearchChannel.java
and annotate the class with @Oskari("channelID")
. This example offers a basic textual search implementation:
package fi.nls.oskari.search;
import fi.mml.portti.service.search.ChannelSearchResult;
import fi.mml.portti.service.search.SearchCriteria;
import fi.mml.portti.service.search.SearchResultItem;
import fi.nls.oskari.annotation.Oskari;
import fi.nls.oskari.search.channel.SearchChannel;
import fi.nls.oskari.util.IOHelper;
import java.io.IOException;
@Oskari("MyChannel")
public class CustomChannel extends SearchChannel {
public ChannelSearchResult doSearch(SearchCriteria criteria) {
ChannelSearchResult result = new ChannelSearchResult();
try {
// TODO: do the actual search
final String responseData = IOHelper.getURL("https://www.google.fi/?q="
+ criteria.getSearchString());
// parse responseData and populate result with SearchResultItems
SearchResultItem item = new SearchResultItem();
item.setTitle("MySearchResult");
result.addItem(item);
}
catch (IOException ex) {
throw new RuntimeException("Error searching", ex);
}
return result;
}
}
Once the class is in the servers classpath the search channel is available in searches as channel with id MyChannel
.
Note! The results location (coordinates) should be in the same projection as specified in the criteria!
9.9.1 SearchChannel methods to override
void init();
Any initialization should be performed here. Properties setup for example.
boolean hasPermission(User user)
Default implementation returns true for all users. Override if the datasource should only be available for some users.
Default search channels
Default channels are used for searching when no channel has been specified for the search. Search channels can specify if they should be included to be used in such queries. This can be done by returning a boolean value from the SearchChannel.isDefaultChannel()
method. The default is true and can be configured with channel specific properties:
public boolean isDefaultChannel() {
return PropertyUtil.getOptional("search.channel." + getName() + ".isDefault", true);
}
SearchableChannel.Capabilities getCapabilities();
Capabilities is an enum with values COORD, TEXT and BOTH. Defaults to TEXT on SearchChannel baseclass.
- TEXT means the channel can be used to search with text.
- COORD means the channel can be used to search with coordinates/reverse geocode.
- BOTH means the channel implements both text and coordinate based searches.
boolean isValidSearchTerm(SearchCriteria criteria);
This method can be overridden to check whether the criteria makes sense in the context of the channel. Like is the searchtext in correct syntax for a cadastral parcel id etc.
ChannelSearchResult reverseGeocode(SearchCriteria criteria) throws IllegalSearchCriteriaException;
Implement this method if you want to use reverse geocoding for the channel. You will also need to override getCapabilities to return COORD or BOTH.
9.9.2 More examples
There is an example search channel for OpenStreetMap available in the
file service-search-opendata/src/main/java/fi/nls/oskari/search/OpenStreetMapSearchService.java
and several more in service-search-nls.
9.10 ChannelProvider customizations
Search channels can sometimes be generated based on some configuration and need to be created dynamically. One example of this in Oskari is configuring registered WFS-services as datasources for search (see WFSChannelProvider
below).
Custom ChannelProvider
Creating a channel provider needs to extend fi.nls.oskari.search.channel.ChannelProvider
and implement the getChannels()
method. The method should return a set of SearchChannel
objects.
package fi.nls.oskari.search.channel;
import fi.nls.oskari.annotation.Oskari;
import java.util.HashSet;
import java.util.Set;
@Oskari
public class WFSChannelProvider extends ChannelProvider {
public Set getChannels() {
Set channels = new HashSet<>();
// TODO: populate set with SearchChannels
return channels;
}
}
The provider also implements a simple listener interface for observing channel changes. The search service registers as a listener to adjust usable search channels based on currently available ones as they can change at runtime. The provider class needs to be annoted with @Oskari
for search service to find it in the classpath.
The search functionality should be notified of any runtime changes to channels the provider creates by calling the channelAdded()
and channelRemoved()
methods on the provider.
WFSChannelProvider
There is an admin bundle available (tampere/admin-wfs-search-channel
) providing user interface for selecting a registered wfs-service and attribute(s) in that service to query against for results. The backend that is managing the WFS channels is implemented as a ChannelProvider. Check fi.nls.oskari.search.channel.WFSChannelProvider
in oskari-server/service-search-wfs
for the implementation. This reads the database for configurations based on registered WFS-services and creates a set of WFSSearchChannel
objects to be used as datasources. These can be further customized by creating WFSChannelHandlers
. WFSChannelHandler can be selected with database table oskari_wfs_search_channels
column config
with JSON value like this:
{
"handler" : "ExampleHandlerID"
}
WFSChannelHandler
The WFSChannelHandlers can be used to modify the WFS-filter that is used when calling the service by overriding the createFilter()
method.
package fi.nls.oskari.search.channel;
import fi.mml.portti.service.search.SearchCriteria;
import fi.nls.oskari.annotation.Oskari;
import fi.nls.oskari.wfs.WFSSearchChannelsConfiguration;
import java.util.List;
@Oskari("ExampleHandlerID")
public class ExampleHandler extends WFSChannelHandler {
private Logger log = LogFactory.getLogger(this.getClass());
public String createFilter(SearchCriteria sc, WFSSearchChannelsConfiguration config) {
// custom filter handling
String searchStr = sc.getSearchString();
StringBuffer filter = new StringBuffer("{{Filter}}");
// TODO: create filter contents
filter.append("{{/Filter}}");
return filter.toString().trim();
}
}
9.11 WFS search functionality deployment
For the oskari-server
make sure you have the dependency providing the functionality included in the maven pom.xml
:
fi.nls.oskari.service
oskari-search-wfs
${oskari.version}
The frontend code for getting the administrative user interface is provided by admin-wfs-search-channel
in the tampere
namespace. You can add configure it as an admin bundles in oskari-ext.properties
by adding it to the dynamic bundles list:
## bundles that are added on runtime to view if user has one of configured role
actionhandler.GetAppSetup.dynamic.bundles = ... any other bundles..., admin-wfs-search-channel
## Linking dynamic bundles based on user roles
## Properties are named 'actionhandler.GetAppSetup.dynamic.[BUNDLE ID].roles'
## with value as comma-separated list of role names that should be served the bundle
actionhandler.GetAppSetup.dynamic.bundle.admin-wfs-search-channel.roles = Admin
This way the bundle will start for any users that have the role named Admin
.
9.11.1 Customized search user interface
There is a more specific user interface for end-user searching that goes well with this functionality: search-from-channels
in the tampere
namespace. It provides an advanced options panel for the end-user to select which channels to use in a search. You can test it out by adding it to the dynamic bundles setup as with the admin above and if you like it you can add it to all the default/user views for the system in the database. You can use the Flyway-script below in your application specific flyway-module (replace V1_0_0
with the next available version):
package flyway.sample;
import fi.nls.oskari.util.FlywayHelper;
import org.flywaydb.core.api.migration.jdbc.JdbcMigration;
import java.sql.Connection;
import java.util.List;
/**
* Adds search-from-channels bundle to default and user views.
*/
public class V1_0_0__add_search-from-channels_to_default_views implements JdbcMigration {
private static final String BUNDLE_ID = "search-from-channels";
public void migrate(Connection connection) throws Exception {
final List views = FlywayHelper.getUserAndDefaultViewIds(connection);
for(Long viewId : views){
if (FlywayHelper.viewContainsBundle(connection, BUNDLE_ID, viewId)) {
continue;
}
FlywayHelper.addBundleWithDefaults(connection, viewId, BUNDLE_ID);
}
}
}
More information about Flyway can be found in here. Or you can add it to a single view by manually running an SQL like this:
INSERT INTO oskari_appsetup_bundles (appsetup_id, seqno, bundle_id, bundleinstance, config, state)
VALUES ([appsetup_id],
(SELECT (max(seqno) + 1) FROM oskari_appsetup_bundles WHERE appsetup_id = [appsetup_id]),
(SELECT id FROM oskari_bundle WHERE name = 'search-from-channels'),
(SELECT name FROM oskari_bundle WHERE name = 'search-from-channels'),
(SELECT config FROM oskari_bundle WHERE name = 'search-from-channels'),
(SELECT state FROM oskari_bundle WHERE name = 'search-from-channels'));
By replacing [appsetup_id]
with the id of the appsetup you want to use it in.
9.12 How to configure map layers
Note! This page is not complete but serves as a starting point for accumulating documentation for different configuration options.
Oskari servers as a registry for different kinds of spatial data infrastructure services. For example actual data for map layers are not stored in Oskari but layers are registered to Oskari so that they point to for example an existing OGC compliant data source.
Most of the registry data that is stored for such services is stored in the database table oskari_maplayer
.
Most of the toggles that an admin can change in oskari_maplayer
are located in the attributes
column. The value of the column is a JSON-object and can have the following keys:
forceProxy
: true | false When true even layers that don't require authentication are proxied through the server. Without this the browser gets the data directly from a WMS/WMTS or similar endpoint.
The attributes column can also have configuration specific to a layer type.
See:
9.13 Configuration options for vector feature layers
Vector features can come from WFS or OGC API Features endpoint and displaying them can be customized with a config on the database table oskari_maplayer
.
For example localized names can be given for feature properties with a JSON-configuration on the attributes
column.
For example consider a GeoJSON presentation of a vector feature like this:
{
geometry: {...},
properties: {
NAME: 'Place name',
LINK: 'https://my.place/241',
IMG: 'https://my.place/241.png'
RENT: '100€/day',
DESCRIPTION: 'This is a nice place'
}
}
You could configure oskari_maplayer.attributes
with:
{
"data": {
"filter": [
"NAME",
"IMG",
"DESCRIPTION",
"RENT"
"LINK"
],
"locale": {
"en": {
"RENT": "Rental price",
"LINK": "More information"
},
"fi": {
"RENT": "Vuokrahinta",
"LINK": "Lisätietoja"
}
},
"format": {
"NAME": {
"type": "h2",
"noLabel": true
},
"DESCRIPTION": {
"type": "p",
"noLabel": true
},
"IMG": {
"type": "image",
"noLabel": true,
"params": {
"link": true
}
},
"LINK": {
"type": "link"
}
}
},
"maxFeatures": 1000,
"namespaceURL": "http://www.oskari.org"
}
Where:
data
see below for details optionalmaxFeatures
limit for features that is requested from service (defaults to ?) optionalnamespaceURL
The feature namespace URI (needs more details where this is used) optional
9.13.1 Data
filter
configures what properties are shown and in what order (refers to actual property names of the feature)locale
is an object with keys based on language codes. The language objects are used to map a user-friendly name for the actual properties name.format
has keys based on the property names with objects that can definetype
to decide how to show/format the property value. It can also includenoLabel
property if you don't want to show the property label to the user and only show the value of the property.
Filter
You can also configure the filter with language specific handling:
{
"data": {
"filter": {
"default": [
"name",
"link",
"image_url"
],
"fi": [
"name_fi",
"link",
"image_url"
],
"en": [
"name_en",
"link",
"image_url"
]
},
...
}
}
Format
Recognized types include:
link
renders an a-tag using the value as href-attributeimage
renders an img-tags using the value for src-attribute. The image can also be wrapped to a link-tag so it can be opened in another tab using the params option seen above.- html-tags like h1-h5, p, i, b, em
An application based on Oskari can also add handling for additional types in their code base with ValueFormatters:
import { setFormatter } from './path/to/ValueFormatters';
setFormatter('title', (value) => `10 ${value}
`);
The above would add a formatter for type title
that wraps the value to an h1-tag.