Improve Region Maps for use without network connection (#15056)

* enable turning on/off of warnings

* add tooltip

* add server setting so users can opt out of connecting to the EMS service

* replace WMS options with custom directive, so we can reuse it in region maps

* add wms settings form to Region Map

* no need to recreate map after leaflet upgrade

* add WMS map to region map

* linting issues

* fix rebase

* improve wording

* add global config

* fix typo
This commit is contained in:
Peter Pisljar 2017-11-21 08:27:39 +01:00 committed by GitHub
parent 319832774a
commit c56360b908
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 209 additions and 125 deletions

View file

@ -97,6 +97,9 @@ The following example shows a valid regionmap configuration.
description: "Full department name"
- name: "INSEE"
description: "INSEE numeric identifier"
`regionmap.includeElasticMapsService`:: turns on or off whether layers from the Elastic Maps Service should be included in the vector layer option list.
By turning this off, only the layers that are configured here will be included.
`server.basePath:`:: Enables you to specify a path to mount Kibana at if you are running behind a proxy. This only affects
the URLs generated by Kibana, your proxy is expected to remove the basePath value before forwarding requests

View file

@ -32,7 +32,6 @@ module.controller('KbnRegionMapController', function ($scope, $element, Private,
});
$scope.$watch('esResponse', async function (tableGroup) {
kibanaMapReady.then(() => {
let results;
@ -52,7 +51,6 @@ module.controller('KbnRegionMapController', function ($scope, $element, Private,
}
updateChoroplethLayer($scope.vis.params.selectedLayer.url, $scope.vis.params.selectedLayer.attribution);
const metricsAgg = _.first($scope.vis.getAggConfig().bySchemaName.metric);
choroplethLayer.setMetrics(results, metricsAgg);
setTooltipFormatter();
@ -78,6 +76,10 @@ module.controller('KbnRegionMapController', function ($scope, $element, Private,
choroplethLayer.setColorRamp(truncatedColorMaps[visParams.colorSchema]);
setTooltipFormatter();
updateBaseLayer(visParams);
kibanaMap.setShowTooltip(visParams.addTooltip);
kibanaMap.setLegendPosition(visParams.legendPosition);
kibanaMap.useUiStateFromVisualization($scope.vis);
@ -86,25 +88,44 @@ module.controller('KbnRegionMapController', function ($scope, $element, Private,
});
});
async function makeKibanaMap() {
async function updateBaseLayer(visParams) {
const tmsSettings = await serviceSettings.getTMSService();
const minMaxZoom = tmsSettings.getMinMaxZoom(false);
let baseLayerOptions;
let minMaxZoom;
if (visParams.wms.enabled) {
minMaxZoom = tmsSettings.getMinMaxZoom(true);
baseLayerOptions = {
baseLayerType: 'wms',
options: {
minZoom: minMaxZoom.minZoom,
maxZoom: minMaxZoom.maxZoom,
url: visParams.wms.url,
...visParams.wms.options
}
};
} else {
minMaxZoom = tmsSettings.getMinMaxZoom(false);
const tmsUrl = tmsSettings.getUrl();
const tmsOptions = tmsSettings.getTMSOptions();
baseLayerOptions = { baseLayerType: 'tms', options: { tmsUrl, ...tmsOptions } };
}
kibanaMap.setMinZoom(minMaxZoom.minZoom);
kibanaMap.setMaxZoom(minMaxZoom.maxZoom);
kibanaMap.setBaseLayer(baseLayerOptions);
}
const options = { ...minMaxZoom };
async function makeKibanaMap() {
const options = {};
const uiState = $scope.vis.getUiState();
const zoomFromUiState = parseInt(uiState.get('mapZoom'));
const centerFromUIState = uiState.get('mapCenter');
options.zoom = !isNaN(zoomFromUiState) ? zoomFromUiState : DEFAULT_ZOOM_SETTINGS.zoom;
options.center = centerFromUIState ? centerFromUIState : DEFAULT_ZOOM_SETTINGS.mapCenter;
kibanaMap = new KibanaMap($element[0], options);
const tmsUrl = tmsSettings.getUrl();
const tmsOptions = tmsSettings.getTMSOptions();
kibanaMap.setBaseLayer({ baseLayerType: 'tms', options: { tmsUrl, ...tmsOptions } });
kibanaMap.addLegendControl();
kibanaMap.addFitControl();
kibanaMap.persistUiStateForVisualization($scope.vis);
await updateBaseLayer($scope.vis.params);
}
function setTooltipFormatter() {
@ -136,11 +157,12 @@ module.controller('KbnRegionMapController', function ($scope, $element, Private,
$scope.vis.API.events.filter({ point: { aggConfigResult: aggConfigResult } });
});
choroplethLayer.on('styleChanged', function (event) {
if (event.mismatches.length > 0 && config.get('visualization:regionmap:showWarnings')) {
const shouldShowWarning = $scope.vis.params.isDisplayWarning && config.get('visualization:regionmap:showWarnings');
if (event.mismatches.length > 0 && shouldShowWarning) {
notify.warning(
`Could not show ${event.mismatches.length} ${event.mismatches.length > 1 ? 'results' : 'result'} on the map.`
+ ` To avoid this, ensure that each term can be joined to a corresponding shape on that shape's join field.`
+ ` Could not join following terms: ${event.mismatches.join(',')}`
+ ` To avoid this, ensure that each term can be matched to a corresponding shape on that shape's join field.`
+ ` Could not match following terms: ${event.mismatches.join(',')}`
);
}
});

View file

@ -10,7 +10,7 @@ import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
import { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps';
import { mapToLayerWithId } from './util';
VisTypesRegistryProvider.register(function RegionMapProvider(Private, regionmapsConfig) {
VisTypesRegistryProvider.register(function RegionMapProvider(Private, regionmapsConfig, config) {
const VisFactory = Private(VisFactoryProvider);
const Schemas = Private(VisSchemasProvider);
@ -32,7 +32,9 @@ VisTypesRegistryProvider.register(function RegionMapProvider(Private, regionmaps
addTooltip: true,
colorSchema: 'Yellow to Red',
selectedLayer: selectedLayer,
selectedJoinField: selectedJoinField
selectedJoinField: selectedJoinField,
isDisplayWarning: true,
wms: config.get('visualization:tileMap:WMSdefaults')
},
template: regionTemplate,
},

View file

@ -34,10 +34,21 @@
<option value=''>Select</option></select>
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="displayWarnings">
Display warnings &nbsp;
<kbn-info info="Turns on/off warnings. When turned on, warning will be shown for each term that cannot be matched to a shape in the vector layer based on the join field. When turned off, these warnings will be turned off."></kbn-info>
</label>
<div class="kuiSideBarFormRow__control">
<input id="displayWarnings" type="checkbox" ng-model="vis.params.isDisplayWarning">
</div>
</div>
</div>
</div>
<div class="kuiSideBarSection">
<div class="kuiSideBarSectionTitle">
<div class="kuiSideBarSectionTitle__text">Style settings</div>
</div>
@ -56,9 +67,12 @@
</div>
</div>
<div class="kuiSideBarSection">
<div class="kuiSideBarSectionTitle">
<div class="kuiSideBarSectionTitle__text">Basic settings</div>
<div class="kuiSideBarSectionTitle__text">
Base Layer Settings
</div>
</div>
<vislib-basic-options></vislib-basic-options>
<wms-options></wms-options>
</div>

View file

@ -1,10 +1,10 @@
import { uiModules } from 'ui/modules';
import regionMapVisParamsTemplate from './region_map_vis_params.html';
import { mapToLayerWithId } from './util';
import '../../tile_map/public/editors/wms_options';
uiModules.get('kibana/region_map')
.directive('regionMapVisParams', function (serviceSettings, Notifier) {
.directive('regionMapVisParams', function (serviceSettings, regionmapsConfig, Notifier) {
const notify = new Notifier({ location: 'Region map' });
@ -14,37 +14,54 @@ uiModules.get('kibana/region_map')
link: function ($scope) {
$scope.collections = $scope.vis.type.editorConfig.collections;
$scope.onLayerChange = onLayerChange;
serviceSettings.getFileLayers()
.then(function (layersFromService) {
layersFromService = layersFromService.map(mapToLayerWithId.bind(null, 'elastic_maps_service'));
const newVectorLayers = $scope.collections.vectorLayers.slice();
for (let i = 0; i < layersFromService.length; i += 1) {
const layerFromService = layersFromService[i];
const alreadyAdded = newVectorLayers.some((layer) => layerFromService.layerId === layer.layerId);
if (!alreadyAdded) {
newVectorLayers.push(layerFromService);
if (regionmapsConfig.includeElasticMapsService) {
serviceSettings.getFileLayers()
.then(function (layersFromService) {
layersFromService = layersFromService.map(mapToLayerWithId.bind(null, 'elastic_maps_service'));
const newVectorLayers = $scope.collections.vectorLayers.slice();
for (let i = 0; i < layersFromService.length; i += 1) {
const layerFromService = layersFromService[i];
const alreadyAdded = newVectorLayers.some((layer) => layerFromService.layerId === layer.layerId);
if (!alreadyAdded) {
newVectorLayers.push(layerFromService);
}
}
}
$scope.collections.vectorLayers = newVectorLayers;
if ($scope.collections.vectorLayers[0] && !$scope.vis.params.selectedLayer) {
$scope.vis.params.selectedLayer = $scope.collections.vectorLayers[0];
onLayerChange();
}
$scope.collections.vectorLayers = newVectorLayers;
if ($scope.collections.vectorLayers[0] && !$scope.vis.params.selectedLayer) {
$scope.vis.params.selectedLayer = $scope.collections.vectorLayers[0];
onLayerChange();
}
//the dirty flag is set to true because the change in vector layers config causes an update of the scope.params
//temp work-around. addressing this issue with the visualize refactor for 6.0
setTimeout(function () {
$scope.dirty = false;
}, 0);
//the dirty flag is set to true because the change in vector layers config causes an update of the scope.params
//temp work-around. addressing this issue with the visualize refactor for 6.0
setTimeout(function () {
$scope.dirty = false;
}, 0);
$scope.collections.vectorLayers = newVectorLayers;
})
.catch(function (error) {
notify.warning(error.message);
});
if ($scope.collections.vectorLayers[0] && !$scope.vis.params.selectedLayer) {
$scope.vis.params.selectedLayer = $scope.collections.vectorLayers[0];
onLayerChange();
}
//the dirty flag is set to true because the change in vector layers config causes an update of the scope.params
//temp work-around. addressing this issue with the visualize refactor for 6.0
setTimeout(function () {
$scope.dirty = false;
}, 0);
})
.catch(function (error) {
notify.warning(error.message);
});
}
function onLayerChange() {
$scope.vis.params.selectedJoinField = $scope.vis.params.selectedLayer.fields[0];

View file

@ -53,77 +53,5 @@
</label>
</div>
<div class="vis-option-item form-group">
<label>
<input type="checkbox"
name="wms.enabled"
ng-model="vis.params.wms.enabled">
WMS compliant map server
<kbn-info info="Use WMS compliant map tile server. For advanced users only."></kbn-info>
</label>
</div>
<div ng-show="vis.params.wms.enabled" class="well">
<div class="vis-option-item form-group">
<p>WMS is an OGC standard for map image services. For more information, go <a href="http://www.opengeospatial.org/standards/wms">here</a>.</p>
<label>
WMS url* <kbn-info info="The URL of the WMS web service."></kbn-info>
</label>
<input type="text" class="form-control"
name="wms.url"
ng-model="vis.params.wms.url">
</div>
<div class="vis-option-item form-group">
<label>
WMS layers* <kbn-info info="A comma seperated list of layers to use."></kbn-info>
</label>
<input type="text" class="form-control"
ng-require="vis.params.wms.enabled"
ng-model="vis.params.wms.options.layers"
name="wms.options.layers">
</div>
<div class="vis-option-item form-group">
<label>
WMS version* <kbn-info info="The version of WMS the server supports"></kbn-info>
</label>
<input type="text" class="form-control"
name="wms.options.version"
ng-model="vis.params.wms.options.version">
</div>
<div class="vis-option-item form-group">
<label>
WMS format* <kbn-info info="Usually image/png or image/jpeg. Use png if the server will return transparent layers."></kbn-info>
</label>
<input type="text" class="form-control"
name="wms.options.format"
ng-model="vis.params.wms.options.format">
</div>
<div class="vis-option-item form-group">
<label>
WMS attribution <kbn-info info="Attribution string for the lower right corner."></kbn-info>
</label>
<input type="text" class="form-control"
name="wms.options.attribution"
ng-model="vis.params.wms.options.attribution">
</div>
<div class="vis-option-item form-group">
<label>
WMS styles* <kbn-info info="A comma seperated list of WMS server supported styles to use. Blank in most cases."></kbn-info>
</label>
<input type="text" class="form-control"
name="wms.options.styles"
ng-model="vis.params.wms.options.styles">
</div>
<p>* if this parameter is incorrect, maps will fail to load.</p>
</div>
<wms-options></wms-options>
</div>

View file

@ -0,0 +1,76 @@
<div>
<div class="vis-option-item form-group">
<label>
<input type="checkbox"
name="wms.enabled"
ng-model="vis.params.wms.enabled">
WMS compliant map server
<kbn-info info="Use WMS compliant map tile server. For advanced users only."></kbn-info>
</label>
</div>
<div ng-show="vis.params.wms.enabled" class="well">
<div class="vis-option-item form-group">
<p>WMS is an OGC standard for map image services. For more information, go <a href="http://www.opengeospatial.org/standards/wms">here</a>.</p>
<label>
WMS url* <kbn-info info="The URL of the WMS web service."></kbn-info>
</label>
<input type="text" class="form-control"
name="wms.url"
ng-model="vis.params.wms.url">
</div>
<div class="vis-option-item form-group">
<label>
WMS layers* <kbn-info info="A comma seperated list of layers to use."></kbn-info>
</label>
<input type="text" class="form-control"
ng-require="vis.params.wms.enabled"
ng-model="vis.params.wms.options.layers"
name="wms.options.layers">
</div>
<div class="vis-option-item form-group">
<label>
WMS version* <kbn-info info="The version of WMS the server supports"></kbn-info>
</label>
<input type="text" class="form-control"
name="wms.options.version"
ng-model="vis.params.wms.options.version">
</div>
<div class="vis-option-item form-group">
<label>
WMS format* <kbn-info info="Usually image/png or image/jpeg. Use png if the server will return transparent layers."></kbn-info>
</label>
<input type="text" class="form-control"
name="wms.options.format"
ng-model="vis.params.wms.options.format">
</div>
<div class="vis-option-item form-group">
<label>
WMS attribution <kbn-info info="Attribution string for the lower right corner."></kbn-info>
</label>
<input type="text" class="form-control"
name="wms.options.attribution"
ng-model="vis.params.wms.options.attribution">
</div>
<div class="vis-option-item form-group">
<label>
WMS styles* <kbn-info info="A comma seperated list of WMS server supported styles to use. Blank in most cases."></kbn-info>
</label>
<input type="text" class="form-control"
name="wms.options.styles"
ng-model="vis.params.wms.options.styles">
</div>
<p>* if this parameter is incorrect, maps will fail to load.</p>
</div>
</div>

View file

@ -0,0 +1,11 @@
import { uiModules } from 'ui/modules';
import wmsOptionsTemplate from './wms_options.html';
const module = uiModules.get('kibana');
module.directive('wmsOptions', function () {
return {
restrict: 'E',
template: wmsOptionsTemplate,
replace: true,
};
});

View file

@ -528,6 +528,13 @@ export class KibanaMap extends EventEmitter {
this._updateExtent();
}
setMinZoom(zoom) {
this._leafletMap.setMinZoom(zoom);
}
setMaxZoom(zoom) {
this._leafletMap.setMaxZoom(zoom);
}
getLeafletBaseLayer() {
return this._leafletBaseLayer;

View file

@ -1,12 +1,12 @@
import 'ui/vislib';
import 'plugins/kbn_vislib_vis_types/controls/vislib_basic_options';
import './editors/wms_options';
import $ from 'jquery';
import _ from 'lodash';
import { KibanaMap } from './kibana_map';
import { GeohashLayer } from './geohash_layer';
import { SearchSourceProvider } from 'ui/courier/data_source/search_source';
import { VisAggConfigProvider } from 'ui/vis/agg_config';
// import './lib/service_settings';
import 'ui/vis/map/service_settings';
import './styles/_tilemap.less';
@ -172,6 +172,7 @@ export function MapsVisualizationProvider(serviceSettings, Notifier, getAppState
* called on options change (vis.params change)
*/
async _updateParams() {
const mapParams = this._getMapsParams();
if (_.eq(this._currentParams, mapParams)) {
return;
@ -181,12 +182,13 @@ export function MapsVisualizationProvider(serviceSettings, Notifier, getAppState
const { minZoom, maxZoom } = this._getMinMaxZoom();
if (mapParams.wms.enabled) {
//Switch to WMS
// Switch to WMS
if (maxZoom > this._kibanaMap.getMaxZoomLevel()) {
//need to recreate the map with less restrictive zoom
this._kibanaMap.removeLayer(this._geohashLayer);
this._geohashLayer = null;
this._kibanaMapReady = this._makeKibanaMap();
await this._kibanaMapReady;
this._kibanaMap.setMinZoom(minZoom);
this._kibanaMap.setMaxZoom(maxZoom);
}
this._kibanaMap.setBaseLayer({
@ -200,13 +202,13 @@ export function MapsVisualizationProvider(serviceSettings, Notifier, getAppState
});
} else {
//switch to regular
// switch to tms
if (maxZoom < this._kibanaMap.getMaxZoomLevel()) {
//need to recreate the map with more restrictive zoom level
this._kibanaMap.removeLayer(this._geohashLayer);
this._geohashLayer = null;
this._kibanaMapReady = this._makeKibanaMap();
await this._kibanaMapReady;
this._kibanaMap.setMinZoom(minZoom);
this._kibanaMap.setMaxZoom(maxZoom);
if (this._kibanaMap.getZoomLevel() > maxZoom) {
this._kibanaMap.setZoomLevel(maxZoom);
}
@ -221,6 +223,7 @@ export function MapsVisualizationProvider(serviceSettings, Notifier, getAppState
});
}
}
const geohashOptions = this._getGeohashOptions();
if (!this._geohashLayer || !this._geohashLayer.isReusable(geohashOptions)) {
this._recreateGeohashLayer(this._chartData);

View file

@ -188,6 +188,7 @@ export default () => Joi.object({
}).default()
}).default(),
regionmap: Joi.object({
includeElasticMapsService: Joi.boolean().default(true),
layers: Joi.array().items(Joi.object({
url: Joi.string(),
type: Joi.string(),