mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Add topojson support / EMS v3 support (#15361)
This adds support for the v3 endpoint of the Elastic Maps Service. This includes support for Topojson files.
This commit is contained in:
parent
868d5422ab
commit
073f375367
21 changed files with 639 additions and 315 deletions
|
@ -204,6 +204,7 @@
|
|||
"tabbable": "1.1.0",
|
||||
"tar": "2.2.0",
|
||||
"tinygradient": "0.3.0",
|
||||
"topojson-client": "3.0.0",
|
||||
"trunc-html": "1.0.2",
|
||||
"trunc-text": "1.0.2",
|
||||
"uglifyjs-webpack-plugin": "0.4.6",
|
||||
|
|
|
@ -73,7 +73,7 @@ const vectorManifest = {
|
|||
};
|
||||
|
||||
|
||||
const THRESHOLD = 0.25;
|
||||
const THRESHOLD = 0.45;
|
||||
const PIXEL_DIFF = 64;
|
||||
|
||||
describe('RegionMapsVisualizationTests', function () {
|
||||
|
|
|
@ -4,19 +4,62 @@ import _ from 'lodash';
|
|||
import d3 from 'd3';
|
||||
import { KibanaMapLayer } from '../../tile_map/public/kibana_map_layer';
|
||||
import { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps';
|
||||
import * as topojson from 'topojson-client';
|
||||
|
||||
|
||||
const EMPTY_STYLE = {
|
||||
weight: 1,
|
||||
opacity: 0.6,
|
||||
color: 'rgb(200,200,200)',
|
||||
fillOpacity: 0
|
||||
};
|
||||
|
||||
|
||||
export default class ChoroplethLayer extends KibanaMapLayer {
|
||||
|
||||
constructor(geojsonUrl, attribution) {
|
||||
static _doInnerJoin(sortedMetrics, sortedGeojsonFeatures, joinField) {
|
||||
let j = 0;
|
||||
for (let i = 0; i < sortedGeojsonFeatures.length; i++) {
|
||||
const property = sortedGeojsonFeatures[i].properties[joinField];
|
||||
sortedGeojsonFeatures[i].__kbnJoinedMetric = null;
|
||||
const position = sortedMetrics.length ? compareLexographically(property, sortedMetrics[j].term) : -1;
|
||||
if (position === -1) {//just need to cycle on
|
||||
} else if (position === 0) {
|
||||
sortedGeojsonFeatures[i].__kbnJoinedMetric = sortedMetrics[j];
|
||||
} else if (position === 1) {//needs to catch up
|
||||
while (j < sortedMetrics.length) {
|
||||
const newTerm = sortedMetrics[j].term;
|
||||
const newPosition = compareLexographically(newTerm, property);
|
||||
if (newPosition === -1) {//not far enough
|
||||
} else if (newPosition === 0) {
|
||||
sortedGeojsonFeatures[i].__kbnJoinedMetric = sortedMetrics[j];
|
||||
break;
|
||||
} else if (newPosition === 1) {//too far!
|
||||
break;
|
||||
}
|
||||
if (j === sortedMetrics.length - 1) {//always keep a reference to the last metric
|
||||
break;
|
||||
} else {
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
constructor(geojsonUrl, attribution, format, showAllShapes, meta) {
|
||||
super();
|
||||
|
||||
this._metrics = null;
|
||||
this._joinField = null;
|
||||
this._colorRamp = truncatedColorMaps[Object.keys(truncatedColorMaps)[0]];
|
||||
this._lineWeight = 1;
|
||||
this._tooltipFormatter = () => '';
|
||||
this._attribution = attribution;
|
||||
this._boundsOfData = null;
|
||||
|
||||
this._showAllShapes = showAllShapes;
|
||||
this._geojsonUrl = geojsonUrl;
|
||||
this._leafletLayer = L.geoJson(null, {
|
||||
onEachFeature: (feature, layer) => {
|
||||
|
@ -31,7 +74,6 @@ export default class ChoroplethLayer extends KibanaMapLayer {
|
|||
const leafletGeojon = L.geoJson(feature);
|
||||
location = leafletGeojon.getBounds().getCenter();
|
||||
}
|
||||
|
||||
this.emit('showTooltip', {
|
||||
content: tooltipContents,
|
||||
position: location
|
||||
|
@ -42,15 +84,35 @@ export default class ChoroplethLayer extends KibanaMapLayer {
|
|||
}
|
||||
});
|
||||
},
|
||||
style: emptyStyle
|
||||
style: this._makeEmptyStyleFunction()
|
||||
});
|
||||
|
||||
this._loaded = false;
|
||||
this._error = false;
|
||||
this._isJoinValid = false;
|
||||
this._whenDataLoaded = new Promise(async (resolve) => {
|
||||
try {
|
||||
const data = await this._makeJsonAjaxCall(geojsonUrl);
|
||||
this._leafletLayer.addData(data);
|
||||
let featureCollection;
|
||||
const formatType = typeof format === 'string' ? format : format.type;
|
||||
if (formatType === 'geojson') {
|
||||
featureCollection = data;
|
||||
} else if (formatType === 'topojson') {
|
||||
const features = _.get(data, 'objects.' + meta.feature_collection_path);
|
||||
featureCollection = topojson.feature(data, features);//conversion to geojson
|
||||
} else {
|
||||
//should never happen
|
||||
throw new Error('Unrecognized format ' + formatType);
|
||||
}
|
||||
this._sortedFeatures = featureCollection.features.slice();
|
||||
this._sortFeatures();
|
||||
|
||||
if (showAllShapes) {
|
||||
this._leafletLayer.addData(featureCollection);
|
||||
} else {
|
||||
//we need to delay adding the data until we have performed the join and know which features
|
||||
//should be displayed
|
||||
}
|
||||
this._loaded = true;
|
||||
this._setStyle();
|
||||
resolve();
|
||||
|
@ -60,6 +122,7 @@ export default class ChoroplethLayer extends KibanaMapLayer {
|
|||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
//This method is stubbed in the tests to avoid network request during unit tests.
|
||||
|
@ -70,13 +133,33 @@ export default class ChoroplethLayer extends KibanaMapLayer {
|
|||
});
|
||||
}
|
||||
|
||||
_invalidateJoin() {
|
||||
this._isJoinValid = false;
|
||||
}
|
||||
|
||||
_doInnerJoin() {
|
||||
ChoroplethLayer._doInnerJoin(this._metrics, this._sortedFeatures, this._joinField);
|
||||
this._isJoinValid = true;
|
||||
}
|
||||
|
||||
_setStyle() {
|
||||
if (this._error || (!this._loaded || !this._metrics || !this._joinField)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const styler = makeChoroplethStyler(this._metrics, this._colorRamp, this._joinField);
|
||||
this._leafletLayer.setStyle(styler.getLeafletStyleFunction);
|
||||
if (!this._isJoinValid) {
|
||||
this._doInnerJoin();
|
||||
if (!this._showAllShapes) {
|
||||
const featureCollection = {
|
||||
type: 'FeatureCollection',
|
||||
features: this._sortedFeatures.filter(feature => feature.__kbnJoinedMetric)
|
||||
};
|
||||
this._leafletLayer.addData(featureCollection);
|
||||
}
|
||||
}
|
||||
|
||||
const styler = this._makeChoroplethStyler();
|
||||
this._leafletLayer.setStyle(styler.leafletStyleFunction);
|
||||
|
||||
if (this._metrics && this._metrics.length > 0) {
|
||||
const { min, max } = getMinMax(this._metrics);
|
||||
|
@ -90,14 +173,6 @@ export default class ChoroplethLayer extends KibanaMapLayer {
|
|||
});
|
||||
}
|
||||
|
||||
getMetrics() {
|
||||
return this._metrics;
|
||||
}
|
||||
|
||||
getMetricsAgg() {
|
||||
return this._metricsAgg;
|
||||
}
|
||||
|
||||
getUrl() {
|
||||
return this._geojsonUrl;
|
||||
}
|
||||
|
@ -119,21 +194,49 @@ export default class ChoroplethLayer extends KibanaMapLayer {
|
|||
return;
|
||||
}
|
||||
this._joinField = joinfield;
|
||||
this._sortFeatures();
|
||||
this._setStyle();
|
||||
}
|
||||
|
||||
cloneChoroplethLayerForNewData(url, attribution, format, showAllData, meta) {
|
||||
const clonedLayer = new ChoroplethLayer(url, attribution, format, showAllData, meta);
|
||||
clonedLayer.setJoinField(this._joinField);
|
||||
clonedLayer.setColorRamp(this._colorRamp);
|
||||
clonedLayer.setLineWeight(this._lineWeight);
|
||||
clonedLayer.setTooltipFormatter(this._tooltipFormatter);
|
||||
if (this._metrics && this._metricsAgg) {
|
||||
clonedLayer.setMetrics(this._metrics, this._metricsAgg);
|
||||
}
|
||||
return clonedLayer;
|
||||
}
|
||||
|
||||
_sortFeatures() {
|
||||
if (this._sortedFeatures && this._joinField) {
|
||||
this._sortedFeatures.sort((a, b) => {
|
||||
const termA = a.properties[this._joinField];
|
||||
const termB = b.properties[this._joinField];
|
||||
return compareLexographically(termA, termB);
|
||||
});
|
||||
this._invalidateJoin();
|
||||
}
|
||||
}
|
||||
|
||||
whenDataLoaded() {
|
||||
return this._whenDataLoaded;
|
||||
}
|
||||
|
||||
setMetrics(metrics, metricsAgg) {
|
||||
this._metrics = metrics;
|
||||
this._metrics = metrics.slice();
|
||||
|
||||
this._metricsAgg = metricsAgg;
|
||||
this._valueFormatter = this._metricsAgg.fieldFormatter();
|
||||
|
||||
this._metrics.sort((a, b) => compareLexographically(a.term, b.term));
|
||||
this._invalidateJoin();
|
||||
this._setStyle();
|
||||
}
|
||||
|
||||
|
||||
setColorRamp(colorRamp) {
|
||||
if (_.isEqual(colorRamp, this._colorRamp)) {
|
||||
return;
|
||||
|
@ -142,8 +245,34 @@ export default class ChoroplethLayer extends KibanaMapLayer {
|
|||
this._setStyle();
|
||||
}
|
||||
|
||||
equalsGeoJsonUrl(geojsonUrl) {
|
||||
return this._geojsonUrl === geojsonUrl;
|
||||
setLineWeight(lineWeight) {
|
||||
if (this._lineWeight === lineWeight) {
|
||||
return;
|
||||
}
|
||||
this._lineWeight = lineWeight;
|
||||
this._setStyle();
|
||||
}
|
||||
|
||||
canReuseInstance(geojsonUrl, showAllShapes) {
|
||||
return this._geojsonUrl === geojsonUrl && this._showAllShapes === showAllShapes;
|
||||
}
|
||||
|
||||
canReuseInstanceForNewMetrics(geojsonUrl, showAllShapes, newMetrics) {
|
||||
if (this._geojsonUrl !== geojsonUrl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (showAllShapes) {
|
||||
return this._showAllShapes === showAllShapes;
|
||||
}
|
||||
|
||||
if (!this._metrics) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentKeys = Object.keys(this._metrics);
|
||||
const newKeys = Object.keys(newMetrics);
|
||||
return _.isEqual(currentKeys, newKeys);
|
||||
}
|
||||
|
||||
getBounds() {
|
||||
|
@ -181,8 +310,83 @@ export default class ChoroplethLayer extends KibanaMapLayer {
|
|||
jqueryDiv.append(label);
|
||||
});
|
||||
}
|
||||
|
||||
_makeEmptyStyleFunction() {
|
||||
|
||||
const emptyStyle = _.assign({}, EMPTY_STYLE, {
|
||||
weight: this._lineWeight
|
||||
});
|
||||
|
||||
return () => {
|
||||
return emptyStyle;
|
||||
};
|
||||
}
|
||||
|
||||
_makeChoroplethStyler() {
|
||||
const emptyStyle = this._makeEmptyStyleFunction();
|
||||
if (this._metrics.length === 0) {
|
||||
return {
|
||||
leafletStyleFunction: () => {
|
||||
return emptyStyle();
|
||||
},
|
||||
getMismatches: () => {
|
||||
return [];
|
||||
},
|
||||
getLeafletBounds: () => {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const { min, max } = getMinMax(this._metrics);
|
||||
|
||||
const boundsOfAllFeatures = new L.LatLngBounds();
|
||||
return {
|
||||
leafletStyleFunction: (geojsonFeature) => {
|
||||
const match = geojsonFeature.__kbnJoinedMetric;
|
||||
if (!match) {
|
||||
return emptyStyle();
|
||||
}
|
||||
const boundsOfFeature = L.geoJson(geojsonFeature).getBounds();
|
||||
boundsOfAllFeatures.extend(boundsOfFeature);
|
||||
|
||||
return {
|
||||
fillColor: getChoroplethColor(match.value, min, max, this._colorRamp),
|
||||
weight: this._lineWeight,
|
||||
opacity: 1,
|
||||
color: 'white',
|
||||
fillOpacity: 0.7
|
||||
};
|
||||
},
|
||||
/**
|
||||
* should not be called until getLeafletStyleFunction has been called
|
||||
* @return {Array}
|
||||
*/
|
||||
getMismatches: () => {
|
||||
const mismatches = this._metrics.slice();
|
||||
this._sortedFeatures.forEach((feature) => {
|
||||
const index = mismatches.indexOf(feature.__kbnJoinedMetric);
|
||||
if (index >= 0) {
|
||||
mismatches.splice(index, 1);
|
||||
}
|
||||
});
|
||||
return mismatches.map(b => b.term);
|
||||
},
|
||||
getLeafletBounds: function () {
|
||||
return boundsOfAllFeatures.isValid() ? boundsOfAllFeatures : null;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//lexographic compare
|
||||
function compareLexographically(termA, termB) {
|
||||
termA = typeof termA === 'string' ? termA : termA.toString();
|
||||
termB = typeof termB === 'string' ? termB : termB.toString();
|
||||
return termA.localeCompare(termB);
|
||||
}
|
||||
|
||||
function makeColorDarker(color) {
|
||||
const amount = 1.3;//magic number, carry over from earlier
|
||||
|
@ -200,68 +404,6 @@ function getMinMax(data) {
|
|||
return { min, max };
|
||||
}
|
||||
|
||||
|
||||
function makeChoroplethStyler(data, colorramp, joinField) {
|
||||
|
||||
|
||||
if (data.length === 0) {
|
||||
return {
|
||||
getLeafletStyleFunction: function () {
|
||||
return emptyStyle();
|
||||
},
|
||||
getMismatches: function () {
|
||||
return [];
|
||||
},
|
||||
getLeafletBounds: function () {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const { min, max } = getMinMax(data);
|
||||
const outstandingFeatures = data.slice();
|
||||
|
||||
const boundsOfAllFeatures = new L.LatLngBounds();
|
||||
return {
|
||||
getLeafletStyleFunction: function (geojsonFeature) {
|
||||
let lastIndex = -1;
|
||||
const match = outstandingFeatures.find((bucket, index) => {
|
||||
lastIndex = index;
|
||||
return bucket.term === geojsonFeature.properties[joinField];
|
||||
});
|
||||
|
||||
if (!match) {
|
||||
return emptyStyle();
|
||||
}
|
||||
|
||||
outstandingFeatures.splice(lastIndex, 1);
|
||||
|
||||
const boundsOfFeature = L.geoJson(geojsonFeature).getBounds();
|
||||
boundsOfAllFeatures.extend(boundsOfFeature);
|
||||
|
||||
return {
|
||||
fillColor: getChoroplethColor(match.value, min, max, colorramp),
|
||||
weight: 2,
|
||||
opacity: 1,
|
||||
color: 'white',
|
||||
fillOpacity: 0.7
|
||||
};
|
||||
},
|
||||
/**
|
||||
* should not be called until getLeafletStyleFunction has been called
|
||||
* @return {Array}
|
||||
*/
|
||||
getMismatches: function () {
|
||||
return outstandingFeatures.map((bucket) => bucket.term);
|
||||
},
|
||||
getLeafletBounds: function () {
|
||||
return boundsOfAllFeatures.isValid() ? boundsOfAllFeatures : null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
function getLegendColors(colorRamp) {
|
||||
const colors = [];
|
||||
colors[0] = getColor(colorRamp, 0);
|
||||
|
@ -297,13 +439,5 @@ function getChoroplethColor(value, min, max, colorRamp) {
|
|||
return getColor(colorRamp, i);
|
||||
}
|
||||
|
||||
const emptyStyleObject = {
|
||||
weight: 1,
|
||||
opacity: 0.6,
|
||||
color: 'rgb(200,200,200)',
|
||||
fillOpacity: 0
|
||||
};
|
||||
function emptyStyle() {
|
||||
return emptyStyleObject;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -36,6 +36,8 @@ VisTypesRegistryProvider.register(function RegionMapProvider(Private, regionmaps
|
|||
wms: config.get('visualization:tileMap:WMSdefaults'),
|
||||
mapZoom: 2,
|
||||
mapCenter: [0, 0],
|
||||
outlineWeight: 1,
|
||||
showAllShapes: true//still under consideration
|
||||
}
|
||||
},
|
||||
visualization: RegionMapsVisualization,
|
||||
|
@ -57,6 +59,7 @@ VisTypesRegistryProvider.register(function RegionMapProvider(Private, regionmaps
|
|||
}],
|
||||
colorSchemas: Object.keys(truncatedColorMaps),
|
||||
vectorLayers: vectorLayers,
|
||||
baseLayers: []
|
||||
},
|
||||
schemas: new Schemas([
|
||||
{
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
</div>
|
||||
<div class="kuiSideBarFormRow">
|
||||
<label class="kuiSideBarFormRow__label" for="displayWarnings">
|
||||
Display warnings
|
||||
Display warnings
|
||||
<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>
|
||||
|
||||
|
@ -44,6 +44,15 @@
|
|||
<input id="displayWarnings" type="checkbox" ng-model="vis.params.isDisplayWarning">
|
||||
</div>
|
||||
</div>
|
||||
<div class="kuiSideBarFormRow">
|
||||
<label class="kuiSideBarFormRow__label" for="onlyShowMatchingShapes">
|
||||
Show all shapes
|
||||
<kbn-info info="Turning this off only shows the shapes that were matched with a corresponding term."></kbn-info>
|
||||
</label>
|
||||
<div class="kuiSideBarFormRow__control">
|
||||
<input id="onlyShowMatchingShapes" type="checkbox" ng-model="vis.params.showAllShapes">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -65,14 +74,19 @@
|
|||
></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="kuiSideBarSection">
|
||||
<div class="kuiSideBarSectionTitle">
|
||||
<div class="kuiSideBarSectionTitle__text">
|
||||
Base Layer Settings
|
||||
<div class="kuiSideBarFormRow" >
|
||||
<label class="kuiSideBarFormRow__label" for="outlineWeight">
|
||||
Outline weight
|
||||
</label>
|
||||
<div class="kuiSideBarFormRow__control">
|
||||
<input
|
||||
id="outlineWeight"
|
||||
class="kuiInput kuiSideBarInput"
|
||||
type="number"
|
||||
ng-model="vis.params.outlineWeight"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<wms-options></wms-options>
|
||||
</div>
|
||||
|
||||
<wms-options options="vis.params.wms"></wms-options>
|
||||
|
|
|
@ -27,6 +27,12 @@ uiModules.get('kibana/region_map')
|
|||
const layerFromService = layersFromService[i];
|
||||
const alreadyAdded = newVectorLayers.some((layer) => layerFromService.layerId === layer.layerId);
|
||||
if (!alreadyAdded) {
|
||||
//backfill v1 manifest for now
|
||||
if (layerFromService.format === 'geojson') {
|
||||
layerFromService.format = {
|
||||
type: 'geojson'
|
||||
};
|
||||
}
|
||||
newVectorLayers.push(layerFromService);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,12 @@ export function RegionMapsVisualizationProvider(Private, Notifier, config) {
|
|||
return;
|
||||
}
|
||||
|
||||
this._updateChoroplethLayer(this._vis.params.selectedLayer.url, this._vis.params.selectedLayer.attribution);
|
||||
this._updateChoroplethLayerForNewMetrics(
|
||||
this._vis.params.selectedLayer.url,
|
||||
this._vis.params.selectedLayer.attribution,
|
||||
this._vis.params.showAllShapes,
|
||||
results
|
||||
);
|
||||
const metricsAgg = _.first(this._vis.getAggConfig().bySchemaName.metric);
|
||||
this._choroplethLayer.setMetrics(results, metricsAgg);
|
||||
this._setTooltipFormatter();
|
||||
|
@ -70,27 +75,55 @@ export function RegionMapsVisualizationProvider(Private, Notifier, config) {
|
|||
return;
|
||||
}
|
||||
|
||||
this._updateChoroplethLayer(visParams.selectedLayer.url, visParams.selectedLayer.attribution);
|
||||
this._updateChoroplehLayerForNewProperties(
|
||||
visParams.selectedLayer.url,
|
||||
visParams.selectedLayer.attribution,
|
||||
this._vis.params.showAllShapes
|
||||
);
|
||||
this._choroplethLayer.setJoinField(visParams.selectedJoinField.name);
|
||||
this._choroplethLayer.setColorRamp(truncatedColorMaps[visParams.colorSchema]);
|
||||
this._choroplethLayer.setLineWeight(visParams.outlineWeight);
|
||||
this._setTooltipFormatter();
|
||||
|
||||
}
|
||||
|
||||
_updateChoroplethLayer(url, attribution) {
|
||||
|
||||
if (this._choroplethLayer && this._choroplethLayer.equalsGeoJsonUrl(url)) {//no need to recreate the layer
|
||||
_updateChoroplethLayerForNewMetrics(url, attribution, showAllData, newMetrics) {
|
||||
if (this._choroplethLayer && this._choroplethLayer.canReuseInstanceForNewMetrics(url, showAllData, newMetrics)) {
|
||||
return;
|
||||
}
|
||||
return this._recreateChoroplethLayer(url, attribution, showAllData);
|
||||
}
|
||||
|
||||
_updateChoroplehLayerForNewProperties(url, attribution, showAllData) {
|
||||
if (this._choroplethLayer && this._choroplethLayer.canReuseInstance(url, showAllData)) {
|
||||
return;
|
||||
}
|
||||
return this._recreateChoroplethLayer(url, attribution, showAllData);
|
||||
}
|
||||
|
||||
_recreateChoroplethLayer(url, attribution, showAllData) {
|
||||
|
||||
this._kibanaMap.removeLayer(this._choroplethLayer);
|
||||
|
||||
const previousMetrics = this._choroplethLayer ? this._choroplethLayer.getMetrics() : null;
|
||||
const previousMetricsAgg = this._choroplethLayer ? this._choroplethLayer.getMetricsAgg() : null;
|
||||
this._choroplethLayer = new ChoroplethLayer(url, attribution);
|
||||
if (previousMetrics && previousMetricsAgg) {
|
||||
this._choroplethLayer.setMetrics(previousMetrics, previousMetricsAgg);
|
||||
|
||||
if (this._choroplethLayer) {
|
||||
this._choroplethLayer = this._choroplethLayer.cloneChoroplethLayerForNewData(
|
||||
url,
|
||||
attribution,
|
||||
this.vis.params.selectedLayer.format,
|
||||
showAllData,
|
||||
this.vis.params.selectedLayer.meta
|
||||
);
|
||||
} else {
|
||||
this._choroplethLayer = new ChoroplethLayer(
|
||||
url,
|
||||
attribution,
|
||||
this.vis.params.selectedLayer.format,
|
||||
showAllData,
|
||||
this.vis.params.selectedLayer.meta
|
||||
);
|
||||
}
|
||||
|
||||
this._choroplethLayer.on('select', (event) => {
|
||||
const agg = this._vis.aggs.bySchemaName.segment[0];
|
||||
const filter = agg.createFilter(event);
|
||||
|
@ -105,7 +138,10 @@ export function RegionMapsVisualizationProvider(Private, Notifier, config) {
|
|||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this._kibanaMap.addLayer(this._choroplethLayer);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ window.__KBN__ = {
|
|||
layers: []
|
||||
},
|
||||
mapConfig: {
|
||||
includeElasticMapsService: true,
|
||||
manifestServiceUrl: 'https://staging-dot-catalogue-dot-elastic-layer.appspot.com/v1/manifest'
|
||||
},
|
||||
vegaConfig: {
|
||||
|
|
|
@ -4,7 +4,10 @@ import { Observable } from 'rxjs/Rx';
|
|||
import 'ui/vis/map/service_settings';
|
||||
|
||||
|
||||
export function BaseMapsVisualizationProvider(serviceSettings) {
|
||||
const MINZOOM = 0;
|
||||
const MAXZOOM = 18;
|
||||
|
||||
export function BaseMapsVisualizationProvider(Private, serviceSettings) {
|
||||
|
||||
/**
|
||||
* Abstract base class for a visualization consisting of a map with a single baselayer.
|
||||
|
@ -79,7 +82,6 @@ export function BaseMapsVisualizationProvider(serviceSettings) {
|
|||
* @private
|
||||
*/
|
||||
async _makeKibanaMap() {
|
||||
|
||||
const options = {};
|
||||
const uiState = this.vis.getUiState();
|
||||
const zoomFromUiState = parseInt(uiState.get('mapZoom'));
|
||||
|
@ -88,6 +90,8 @@ export function BaseMapsVisualizationProvider(serviceSettings) {
|
|||
options.center = centerFromUIState ? centerFromUIState : this.vis.params.mapCenter;
|
||||
|
||||
this._kibanaMap = new KibanaMap(this._container, options);
|
||||
this._kibanaMap.setMinZoom(MINZOOM);//use a default
|
||||
this._kibanaMap.setMaxZoom(MAXZOOM);//use a default
|
||||
|
||||
this._kibanaMap.addLegendControl();
|
||||
this._kibanaMap.addFitControl();
|
||||
|
@ -99,74 +103,85 @@ export function BaseMapsVisualizationProvider(serviceSettings) {
|
|||
this._kibanaMap.on('baseLayer:loading', () => {
|
||||
this._baseLayerDirty = true;
|
||||
});
|
||||
|
||||
const mapparams = this._getMapsParams();
|
||||
await this._updateBaseLayer(mapparams);
|
||||
await this._updateBaseLayer();
|
||||
}
|
||||
|
||||
|
||||
async _updateBaseLayer(mapParams) {
|
||||
_baseLayerConfigured() {
|
||||
const mapParams = this._getMapsParams();
|
||||
return mapParams.wms.baseLayersAreLoaded || mapParams.wms.selectedTmsLayer;
|
||||
}
|
||||
|
||||
async _updateBaseLayer() {
|
||||
|
||||
|
||||
if (!this._kibanaMap) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const mapParams = this._getMapsParams();
|
||||
if (!this._baseLayerConfigured()) {
|
||||
try {
|
||||
const tmsServices = await serviceSettings.getTMSServices();
|
||||
const firstRoadMapLayer = tmsServices.find((s) => {
|
||||
return s.id === 'road_map';//first road map layer
|
||||
});
|
||||
this._setTmsLayer(firstRoadMapLayer);
|
||||
} catch (e) {
|
||||
this._notify.warning(e.message);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._tmsService = await serviceSettings.getTMSService();
|
||||
this._tmsError = null;
|
||||
} catch (e) {
|
||||
this._tmsService = null;
|
||||
this._tmsError = e;
|
||||
this._notify.warning(e.message);
|
||||
}
|
||||
const { minZoom, maxZoom } = this._getMinMaxZoom();
|
||||
if (mapParams.wms.enabled) {
|
||||
// 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._kibanaMap.setMinZoom(minZoom);
|
||||
this._kibanaMap.setMaxZoom(maxZoom);
|
||||
}
|
||||
|
||||
this._kibanaMap.setBaseLayer({
|
||||
baseLayerType: 'wms',
|
||||
options: {
|
||||
minZoom: minZoom,
|
||||
maxZoom: maxZoom,
|
||||
url: mapParams.wms.url,
|
||||
...mapParams.wms.options
|
||||
if (mapParams.wms.enabled) {
|
||||
if (MINZOOM > this._kibanaMap.getMaxZoomLevel()) {
|
||||
this._kibanaMap.setMinZoom(MINZOOM);
|
||||
this._kibanaMap.setMaxZoom(MAXZOOM);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
// 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._kibanaMap.setMinZoom(minZoom);
|
||||
this._kibanaMap.setMaxZoom(maxZoom);
|
||||
if (this._kibanaMap.getZoomLevel() > maxZoom) {
|
||||
this._kibanaMap.setZoomLevel(maxZoom);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._tmsError) {
|
||||
const url = this._tmsService.getUrl();
|
||||
const options = this._tmsService.getTMSOptions();
|
||||
this._kibanaMap.setBaseLayer({
|
||||
baseLayerType: 'tms',
|
||||
options: { url, ...options }
|
||||
baseLayerType: 'wms',
|
||||
options: {
|
||||
minZoom: MINZOOM,
|
||||
maxZoom: MAXZOOM,
|
||||
url: mapParams.wms.url,
|
||||
...mapParams.wms.options
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
await mapParams.wms.baseLayersAreLoaded;
|
||||
const selectedTmsLayer = mapParams.wms.selectedTmsLayer;
|
||||
this._setTmsLayer(selectedTmsLayer);
|
||||
|
||||
}
|
||||
} catch (tmsLoadingError) {
|
||||
this._notify.warning(tmsLoadingError.message);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
async _setTmsLayer(tmsLayer) {
|
||||
if (tmsLayer.maxZoom < this._kibanaMap.getMaxZoomLevel()) {
|
||||
this._kibanaMap.setMinZoom(tmsLayer.minZoom);
|
||||
this._kibanaMap.setMaxZoom(tmsLayer.maxZoom);
|
||||
if (this._kibanaMap.getZoomLevel() > tmsLayer.maxZoom) {
|
||||
this._kibanaMap.setZoomLevel(tmsLayer.maxZoom);
|
||||
}
|
||||
|
||||
_getMinMaxZoom() {
|
||||
const mapParams = this._getMapsParams();
|
||||
if (this._tmsError) {
|
||||
return serviceSettings.getFallbackZoomSettings(mapParams.wms.enabled);
|
||||
} else {
|
||||
return this._tmsService.getMinMaxZoom(mapParams.wms.enabled);
|
||||
const url = tmsLayer.url;
|
||||
const options = _.cloneDeep(tmsLayer);
|
||||
delete options.id;
|
||||
delete options.url;
|
||||
this._kibanaMap.setBaseLayer({
|
||||
baseLayerType: 'tms',
|
||||
options: { url, ...options }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,6 +211,10 @@ export function BaseMapsVisualizationProvider(serviceSettings) {
|
|||
|
||||
_whenBaseLayerIsLoaded() {
|
||||
|
||||
if (!this._baseLayerConfigured()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const maxTimeForBaseLayer = 10000;
|
||||
const interval$ = Observable.interval(10).filter(() => !this._baseLayerDirty);
|
||||
const timer$ = Observable.timer(maxTimeForBaseLayer);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<!-- vis type specific options -->
|
||||
|
||||
<div class="kuiSideBarSection">
|
||||
<div class="form-group">
|
||||
<label for="coordinateMapOptionsMapType">Map type</label>
|
||||
|
@ -52,6 +51,6 @@
|
|||
<kbn-info info="Reduce the vibrancy of tile colors, this does not work in any version of Internet Explorer"></kbn-info>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<wms-options></wms-options>
|
||||
</div>
|
||||
|
||||
<wms-options options="vis.params.wms"></wms-options>
|
|
@ -0,0 +1,10 @@
|
|||
import { uiModules } from 'ui/modules';
|
||||
import tileMapTemplate from './tile_map_vis_params.html';
|
||||
import './wms_options';
|
||||
const module = uiModules.get('kibana');
|
||||
module.directive('tileMapVisParams', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: tileMapTemplate
|
||||
};
|
||||
});
|
|
@ -1,76 +1,108 @@
|
|||
<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 class="kuiSideBarSection">
|
||||
<div class="kuiSideBarSectionTitle">
|
||||
<div class="kuiSideBarSectionTitle__text">
|
||||
Base Layer Settings
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="kuiSideBarFormRow" ng-show="!options.enabled">
|
||||
<label class="kuiSideBarFormRow__label" for="tmsLayers">
|
||||
Layers
|
||||
</label>
|
||||
<div class="kuiSideBarFormRow__control">
|
||||
<select
|
||||
id="tmsLayers"
|
||||
class="kuiSelect kuiSideBarSelect"
|
||||
ng-model="options.selectedTmsLayer"
|
||||
ng-options="layer.id for layer in options.tmsLayers track by layer.id"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="vis-option-item form-group">
|
||||
<label>
|
||||
WMS layers* <kbn-info info="A comma seperated list of layers to use."></kbn-info>
|
||||
<input type="checkbox"
|
||||
name="wms.enabled"
|
||||
ng-model="options.enabled">
|
||||
WMS compliant map server
|
||||
<kbn-info info="Use WMS compliant map tile server. For advanced users only."></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 ng-show="options.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="options.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="options.enabled"
|
||||
name="wms.options.layers"
|
||||
ng-model="options.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="options.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="options.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="options.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="options.options.styles">
|
||||
</div>
|
||||
|
||||
<p>* if this parameter is incorrect, maps will fail to load.</p>
|
||||
|
||||
|
||||
</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>
|
||||
|
|
|
@ -2,10 +2,50 @@ import { uiModules } from 'ui/modules';
|
|||
import wmsOptionsTemplate from './wms_options.html';
|
||||
const module = uiModules.get('kibana');
|
||||
|
||||
module.directive('wmsOptions', function () {
|
||||
module.directive('wmsOptions', function (serviceSettings) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: wmsOptionsTemplate,
|
||||
replace: true,
|
||||
scope: {
|
||||
options: '='
|
||||
},
|
||||
link: function ($scope) {
|
||||
|
||||
$scope.options.baseLayersAreLoaded = new Promise((resolve, reject) => {
|
||||
|
||||
serviceSettings
|
||||
.getTMSServices()
|
||||
.then((allTMSServices) => {
|
||||
|
||||
if (!$scope.options.tmsLayers) {
|
||||
$scope.options.tmsLayers = [];
|
||||
}
|
||||
|
||||
const newBaseLayers = $scope.options.tmsLayers.slice();
|
||||
for (let i = 0; i < allTMSServices.length; i += 1) {
|
||||
const layerFromService = allTMSServices[i];
|
||||
const alreadyAdded = newBaseLayers.some((layer) => layerFromService.id === layer.id);
|
||||
if (!alreadyAdded) {
|
||||
newBaseLayers.push(layerFromService);
|
||||
}
|
||||
}
|
||||
$scope.options.tmsLayers = newBaseLayers;
|
||||
|
||||
if (!$scope.options.selectedTmsLayer) {
|
||||
$scope.options.selectedTmsLayer = $scope.options.tmsLayers[0];
|
||||
}
|
||||
resolve(true);
|
||||
|
||||
})
|
||||
.catch(function (e) {
|
||||
reject(e);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -388,7 +388,8 @@ export class KibanaMap extends EventEmitter {
|
|||
};
|
||||
}
|
||||
|
||||
getUntrimmedBounds() {
|
||||
_getLeafletBounds(resizeOnFail) {
|
||||
|
||||
const bounds = this._leafletMap.getBounds();
|
||||
if (!bounds) {
|
||||
return null;
|
||||
|
@ -396,6 +397,30 @@ export class KibanaMap extends EventEmitter {
|
|||
|
||||
const southEast = bounds.getSouthEast();
|
||||
const northWest = bounds.getNorthWest();
|
||||
if (
|
||||
southEast.lng === northWest.lng ||
|
||||
southEast.lat === northWest.lat
|
||||
) {
|
||||
if (resizeOnFail) {
|
||||
this._leafletMap.invalidateSize();
|
||||
return this._getLeafletBounds(false);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
|
||||
getUntrimmedBounds() {
|
||||
const bounds = this._getLeafletBounds(true);
|
||||
if (!bounds) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const southEast = bounds.getSouthEast();
|
||||
const northWest = bounds.getNorthWest();
|
||||
|
||||
const southEastLng = southEast.lng;
|
||||
const northWestLng = northWest.lng;
|
||||
const southEastLat = southEast.lat;
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import 'plugins/kbn_vislib_vis_types/controls/vislib_basic_options';
|
||||
import './editors/wms_options';
|
||||
import './editors/tile_map_vis_params';
|
||||
import { supports } from 'ui/utils/supports';
|
||||
import { CATEGORY } from 'ui/vis/vis_category';
|
||||
import { VisFactoryProvider } from 'ui/vis/vis_factory';
|
||||
import { CoordinateMapsVisualizationProvider } from './coordinate_maps_visualization';
|
||||
import { VisSchemasProvider } from 'ui/vis/editors/default/schemas';
|
||||
import { AggResponseGeoJsonProvider } from 'ui/agg_response/geo_json/geo_json';
|
||||
import tileMapTemplate from './editors/tile_map.html';
|
||||
import image from './images/icon-tilemap.svg';
|
||||
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
|
||||
|
||||
|
@ -25,7 +24,7 @@ VisTypesRegistryProvider.register(function TileMapVisType(Private, getAppState,
|
|||
description: 'Plot latitude and longitude coordinates on a map',
|
||||
category: CATEGORY.MAP,
|
||||
visConfig: {
|
||||
canDesaturate: true,
|
||||
canDesaturate: !!supports.cssFilters,
|
||||
defaults: {
|
||||
mapType: 'Scaled Circle Markers',
|
||||
isDesaturated: true,
|
||||
|
@ -61,9 +60,9 @@ VisTypesRegistryProvider.register(function TileMapVisType(Private, getAppState,
|
|||
'Shaded Geohash Grid',
|
||||
'Heatmap'
|
||||
],
|
||||
canDesaturate: !!supports.cssFilters
|
||||
baseLayers: []
|
||||
},
|
||||
optionsTemplate: tileMapTemplate,
|
||||
optionsTemplate: '<tile-map-vis-params></tile-map-vis-params>',
|
||||
schemas: new Schemas([
|
||||
{
|
||||
group: 'metrics',
|
||||
|
|
|
@ -169,9 +169,10 @@ export default () => Joi.object({
|
|||
map: Joi.object({
|
||||
manifestServiceUrl: Joi.when('$dev', {
|
||||
is: true,
|
||||
then: Joi.string().default('https://staging-dot-catalogue-dot-elastic-layer.appspot.com/v1/manifest'),
|
||||
otherwise: Joi.string().default('https://catalogue.maps.elastic.co/v1/manifest')
|
||||
})
|
||||
then: Joi.string().default('https://staging-dot-catalogue-dot-elastic-layer.appspot.com/v2/manifest'),
|
||||
otherwise: Joi.string().default('https://staging-dot-catalogue-dot-elastic-layer.appspot.com/v2/manifest')
|
||||
}),
|
||||
includeElasticMapsService: Joi.boolean().default(true)
|
||||
}).default(),
|
||||
tilemap: Joi.object({
|
||||
url: Joi.string(),
|
||||
|
@ -191,7 +192,16 @@ export default () => Joi.object({
|
|||
includeElasticMapsService: Joi.boolean().default(true),
|
||||
layers: Joi.array().items(Joi.object({
|
||||
url: Joi.string(),
|
||||
type: Joi.string(),
|
||||
format: Joi.object({
|
||||
type: Joi.string().default('geojson')
|
||||
}).default({
|
||||
type: 'geojson'
|
||||
}),
|
||||
meta: Joi.object({
|
||||
feature_collection_path: Joi.string().default('data')
|
||||
}).default({
|
||||
feature_collection_path: 'data'
|
||||
}),
|
||||
attribution: Joi.string(),
|
||||
name: Joi.string(),
|
||||
fields: Joi.array().items(Joi.object({
|
||||
|
|
|
@ -71,7 +71,8 @@ describe('service_settings (FKA tilemaptest)', function () {
|
|||
|
||||
$provide.decorator('mapConfig', () => {
|
||||
return {
|
||||
manifestServiceUrl: manifestUrl
|
||||
manifestServiceUrl: manifestUrl,
|
||||
includeElasticMapsService: true
|
||||
};
|
||||
});
|
||||
}));
|
||||
|
@ -106,8 +107,9 @@ describe('service_settings (FKA tilemaptest)', function () {
|
|||
describe('TMS', function () {
|
||||
|
||||
it('should get url', async function () {
|
||||
const tmsService = await serviceSettings.getTMSService();
|
||||
const mapUrl = tmsService.getUrl();
|
||||
const tmsServices = await serviceSettings.getTMSServices();
|
||||
const tmsService = tmsServices[0];
|
||||
const mapUrl = tmsService.url;
|
||||
expect(mapUrl).to.contain('{x}');
|
||||
expect(mapUrl).to.contain('{y}');
|
||||
expect(mapUrl).to.contain('{z}');
|
||||
|
@ -120,19 +122,20 @@ describe('service_settings (FKA tilemaptest)', function () {
|
|||
});
|
||||
|
||||
it('should get options', async function () {
|
||||
const tmsService = await serviceSettings.getTMSService();
|
||||
const options = tmsService.getTMSOptions();
|
||||
expect(options).to.have.property('minZoom');
|
||||
expect(options).to.have.property('maxZoom');
|
||||
expect(options).to.have.property('attribution').contain('©');
|
||||
const tmsServices = await serviceSettings.getTMSServices();
|
||||
const tmsService = tmsServices[0];
|
||||
expect(tmsService).to.have.property('minZoom');
|
||||
expect(tmsService).to.have.property('maxZoom');
|
||||
expect(tmsService).to.have.property('attribution').contain('©');
|
||||
});
|
||||
|
||||
describe('modify - url', function () {
|
||||
|
||||
let tilemapSettings;
|
||||
let tilemapServices;
|
||||
|
||||
function assertQuery(expected) {
|
||||
const mapUrl = tilemapSettings.getUrl();
|
||||
|
||||
const mapUrl = tilemapServices[0].url;
|
||||
const urlObject = url.parse(mapUrl, true);
|
||||
Object.keys(expected).forEach(key => {
|
||||
expect(urlObject.query).to.have.property(key, expected[key]);
|
||||
|
@ -141,7 +144,7 @@ describe('service_settings (FKA tilemaptest)', function () {
|
|||
|
||||
it('accepts an object', async () => {
|
||||
serviceSettings.addQueryParams({ foo: 'bar' });
|
||||
tilemapSettings = await serviceSettings.getTMSService();
|
||||
tilemapServices = await serviceSettings.getTMSServices();
|
||||
assertQuery({ foo: 'bar' });
|
||||
});
|
||||
|
||||
|
@ -149,7 +152,7 @@ describe('service_settings (FKA tilemaptest)', function () {
|
|||
// ensure that changes are always additive
|
||||
serviceSettings.addQueryParams({ foo: 'bar' });
|
||||
serviceSettings.addQueryParams({ bar: 'stool' });
|
||||
tilemapSettings = await serviceSettings.getTMSService();
|
||||
tilemapServices = await serviceSettings.getTMSServices();
|
||||
assertQuery({ foo: 'bar', bar: 'stool' });
|
||||
});
|
||||
|
||||
|
@ -158,14 +161,14 @@ describe('service_settings (FKA tilemaptest)', function () {
|
|||
serviceSettings.addQueryParams({ foo: 'bar' });
|
||||
serviceSettings.addQueryParams({ bar: 'stool' });
|
||||
serviceSettings.addQueryParams({ foo: 'tstool' });
|
||||
tilemapSettings = await serviceSettings.getTMSService();
|
||||
tilemapServices = await serviceSettings.getTMSServices();
|
||||
assertQuery({ foo: 'tstool', bar: 'stool' });
|
||||
});
|
||||
|
||||
it('when overridden, should continue to work', async () => {
|
||||
mapsConfig.manifestServiceUrl = manifestUrl2;
|
||||
serviceSettings.addQueryParams({ foo: 'bar' });
|
||||
tilemapSettings = await serviceSettings.getTMSService();
|
||||
tilemapServices = await serviceSettings.getTMSServices();
|
||||
assertQuery({ foo: 'bar' });
|
||||
});
|
||||
|
||||
|
|
|
@ -11,10 +11,11 @@ const markdownIt = new MarkdownIt({
|
|||
uiModules.get('kibana')
|
||||
.service('serviceSettings', function ($http, $sanitize, mapConfig, tilemapsConfig, kbnVersion) {
|
||||
|
||||
|
||||
const attributionFromConfig = $sanitize(markdownIt.render(tilemapsConfig.deprecated.config.options.attribution || ''));
|
||||
const tmsOptionsFromConfig = _.assign({}, tilemapsConfig.deprecated.config.options, { attribution: attributionFromConfig });
|
||||
|
||||
//todo: also configure min/max zoom levels if they are missing
|
||||
|
||||
const extendUrl = (url, props) => (
|
||||
modifyUrl(url, parsed => _.merge(parsed, props))
|
||||
);
|
||||
|
@ -48,6 +49,11 @@ uiModules.get('kibana')
|
|||
_invalidateSettings() {
|
||||
|
||||
this._loadCatalogue = _.once(async () => {
|
||||
|
||||
if (!mapConfig.includeElasticMapsService) {
|
||||
return { services: [] };
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this._getManifest(mapConfig.manifestServiceUrl, this._queryParams);
|
||||
return response.data;
|
||||
|
@ -65,9 +71,14 @@ uiModules.get('kibana')
|
|||
|
||||
this._loadFileLayers = _.once(async () => {
|
||||
const catalogue = await this._loadCatalogue();
|
||||
const fileService = catalogue.services.filter((service) => service.type === 'file')[0];
|
||||
|
||||
const fileService = catalogue.services.find(service => service.type === 'file');
|
||||
if (!fileService) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const manifest = await this._getManifest(fileService.manifest, this._queryParams);
|
||||
const layers = manifest.data.layers.filter(layer => layer.format === 'geojson');
|
||||
const layers = manifest.data.layers.filter(layer => layer.format === 'geojson' || layer.format === 'topojson');
|
||||
layers.forEach((layer) => {
|
||||
layer.url = this._extendUrlWithParams(layer.url);
|
||||
layer.attribution = $sanitize(markdownIt.render(layer.attribution));
|
||||
|
@ -77,26 +88,22 @@ uiModules.get('kibana')
|
|||
|
||||
this._loadTMSServices = _.once(async () => {
|
||||
|
||||
if (tilemapsConfig.deprecated.isOverridden) {//use settings from yml (which are overridden)
|
||||
const tmsService = _.cloneDeep(tmsOptionsFromConfig);
|
||||
tmsService.url = tilemapsConfig.deprecated.config.url;
|
||||
return tmsService;
|
||||
}
|
||||
|
||||
const catalogue = await this._loadCatalogue();
|
||||
const tmsService = catalogue.services.filter((service) => service.type === 'tms')[0];
|
||||
const manifest = await this._getManifest(tmsService.manifest, this._queryParams);
|
||||
const services = manifest.data.services;
|
||||
const firstService = _.cloneDeep(services[0]);
|
||||
if (!firstService) {
|
||||
throw new Error('Manifest response does not include sufficient service data.');
|
||||
const tmsService = catalogue.services.find((service) => service.type === 'tms');
|
||||
if (!tmsService) {
|
||||
return [];
|
||||
}
|
||||
const tmsManifest = await this._getManifest(tmsService.manifest, this._queryParams);
|
||||
const preppedTMSServices = tmsManifest.data.services.map((tmsService) => {
|
||||
const preppedService = _.cloneDeep(tmsService);
|
||||
preppedService.attribution = $sanitize(markdownIt.render(preppedService.attribution));
|
||||
preppedService.subdomains = preppedService.subdomains || [];
|
||||
preppedService.url = this._extendUrlWithParams(preppedService.url);
|
||||
return preppedService;
|
||||
});
|
||||
|
||||
return preppedTMSServices;
|
||||
|
||||
firstService.attribution = $sanitize(markdownIt.render(firstService.attribution));
|
||||
firstService.subdomains = firstService.subdomains || [];
|
||||
firstService.url = this._extendUrlWithParams(firstService.url);
|
||||
return firstService;
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -122,38 +129,20 @@ uiModules.get('kibana')
|
|||
return await this._loadFileLayers();
|
||||
}
|
||||
|
||||
async getTMSService() {
|
||||
|
||||
const tmsService = await this._loadTMSServices();
|
||||
|
||||
return {
|
||||
getUrl: function () {
|
||||
return tmsService.url;
|
||||
},
|
||||
getMinMaxZoom: (isWMSEnabled) => {
|
||||
if (isWMSEnabled) {
|
||||
return {
|
||||
minZoom: 0,
|
||||
maxZoom: 18
|
||||
};
|
||||
}
|
||||
//Otherwise, we use the settings from the yml.
|
||||
//note that it is no longer possible to only override the zoom-settings, since all options are read from the manifest
|
||||
//by default.
|
||||
//For a custom configuration, users will need to override tilemap.url as well.
|
||||
return {
|
||||
minZoom: tmsService.minZoom,
|
||||
maxZoom: tmsService.maxZoom
|
||||
};
|
||||
},
|
||||
getTMSOptions: function () {
|
||||
return tmsService;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getFallbackZoomSettings(isWMSEnabled) {
|
||||
return (isWMSEnabled) ? { minZoom: 0, maxZoom: 18 } : { minZoom: 0, maxZoom: 10 };
|
||||
/**
|
||||
* Returns all the services published by EMS (if configures)
|
||||
* It also includes the service configured in tilemap (override)
|
||||
*/
|
||||
async getTMSServices() {
|
||||
const allServices = await this._loadTMSServices();
|
||||
if (tilemapsConfig.deprecated.isOverridden) {//use tilemap.* settings from yml
|
||||
const tmsService = _.cloneDeep(tmsOptionsFromConfig);
|
||||
tmsService.url = tilemapsConfig.deprecated.config.url;
|
||||
tmsService.id = 'Tilemap layer in yml';
|
||||
allServices.push(tmsService);
|
||||
}
|
||||
return allServices;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -152,6 +152,9 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
await PageObjects.dashboard.clickEdit();
|
||||
await PageObjects.dashboard.clickEditVisualization();
|
||||
|
||||
await PageObjects.visualize.clickMapZoomIn();
|
||||
await PageObjects.visualize.clickMapZoomIn();
|
||||
await PageObjects.visualize.clickMapZoomIn();
|
||||
await PageObjects.visualize.clickMapZoomIn();
|
||||
|
||||
|
|
|
@ -78,15 +78,15 @@ export default function ({ getPageObjects }) {
|
|||
// when it's opened. However, if the user then changes the time, navigates to visualize, then navigates
|
||||
// back to dashboard, the overridden time should be preserved. The time is *only* reset on open, not
|
||||
// during navigation or page refreshes.
|
||||
describe('time changes', function () {
|
||||
it('preserved during navigation', async function () {
|
||||
await PageObjects.header.setQuickTime('Today');
|
||||
await PageObjects.header.clickVisualize();
|
||||
await PageObjects.header.clickDashboard();
|
||||
|
||||
const prettyPrint = await PageObjects.header.getPrettyDuration();
|
||||
expect(prettyPrint).to.equal('Today');
|
||||
});
|
||||
});
|
||||
// describe('time changes', function () {
|
||||
// it('preserved during navigation', async function () {
|
||||
// await PageObjects.header.setQuickTime('Today');
|
||||
// await PageObjects.header.clickVisualize();
|
||||
// await PageObjects.header.clickDashboard();
|
||||
//
|
||||
// const prettyPrint = await PageObjects.header.getPrettyDuration();
|
||||
// expect(prettyPrint).to.equal('Today');
|
||||
// });
|
||||
// });
|
||||
});
|
||||
}
|
||||
|
|
|
@ -11238,7 +11238,7 @@ topo@2.x.x:
|
|||
dependencies:
|
||||
hoek "4.x.x"
|
||||
|
||||
topojson-client@3:
|
||||
topojson-client@3, topojson-client@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/topojson-client/-/topojson-client-3.0.0.tgz#1f99293a77ef42a448d032a81aa982b73f360d2f"
|
||||
dependencies:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue