diff --git a/src/config.xml b/src/config.xml index cff8106..19c0c4b 100644 --- a/src/config.xml +++ b/src/config.xml @@ -1,7 +1,7 @@ - +
- OpenLayers Map + OpenLayers Map ja wirecloud@letsfiware.jp https://github.com/lets-fiware/ol-map-ja-widget images/catalogue.png @@ -20,12 +20,100 @@ - - - - - - + + + + + + + + + + + + + diff --git a/src/css/styles.css b/src/css/styles.css index 8490fb1..088af20 100644 --- a/src/css/styles.css +++ b/src/css/styles.css @@ -14,3 +14,9 @@ body, html, .map { justify-content: center; align-items: center; } + +#swipe { + position: absolute; + bottom: 40px; + width 90%; +} diff --git a/src/doc/changelog.md b/src/doc/changelog.md index df7003a..4d9dfdb 100644 --- a/src/doc/changelog.md +++ b/src/doc/changelog.md @@ -1,3 +1,21 @@ +## v1.0.0 (2021-05-16) + +- ol map ja v1.0.0 +- Add Japan GSI map feature +- Add overview map feature +- Add scale line feature +- Add swipe feature +- Add heatmap layer +- Add heatmap KML layer +- Add removePoI feature +- Add feature for building marker with Font Awesome icon +- Add custom style option with image or text +- Add initial location value and initial zoom Level value + +NOTE: This WireCloud widget is based on Wirecloud/ol3-map-widget created by +CoNWeT Lab., Universidad Politecnica de Madrid and Future Internet Consulting +and Development Solutions S.L.. + ## v1.2.3 (2021-03-25) - Fix some problems rendering the layer/setcenter buttons diff --git a/src/doc/userguide.md b/src/doc/userguide.md index 920fcd4..574df14 100644 --- a/src/doc/userguide.md +++ b/src/doc/userguide.md @@ -78,6 +78,22 @@ Map viewer widget using OpenLayers. It can receive Layers or Point of Interest d - `opacity`: Opacity of the icon (range from 0 to 1). Default is `1`. - `scale`: Scale. Default is `1`. - `src`: Image source URI. + - `fontawesome`: Options of the Font Awesome icon to use for the marker. + Supported options: + - `glyph`: a glyph of the icon. Default is `fa-star`. + - `form`: icon form (icon, circle, box, marker). Default is + `marker`. + - `size`: pixel size of the icon. Default is `16` px. + - `color`: icon color. Default is same as the stroke color. CSS3 + color, that is, an hexadecimal, `rgb` or `rgba` color. Default is + `white`. + - `fill`: fill color. CSS3 color, that is, an hexadecimal, `rgb` or + `rgba` color. Default is `blue`. + - `stroke`: stroke color. CSS3 color, that is, an hexadecimal, `rgb` + or `rgba` color. Default is `white`. + - `strokeWidth`: width of stroke. Default is `3`. + - `radius`: radius of marker. + - `margin`: margin between icon and marker. - `iconHighlighted`: icon configuration to use when the PoI is selected. Works in the same way than the `icon` field. - `infoWindow`: content (using HTML) associated with the PoI. diff --git a/src/images/catalogue.png b/src/images/catalogue.png index 2bf9256..9c3b317 100644 Binary files a/src/images/catalogue.png and b/src/images/catalogue.png differ diff --git a/src/index.html b/src/index.html index a078fca..980e6b6 100644 --- a/src/index.html +++ b/src/index.html @@ -17,6 +17,7 @@ + diff --git a/src/js/ol3-map-widget.js b/src/js/ol3-map-widget.js index 9462d9b..1cad1f9 100644 --- a/src/js/ol3-map-widget.js +++ b/src/js/ol3-map-widget.js @@ -28,6 +28,168 @@ }; var CORE_LAYERS = { + // https://maps.gsi.go.jp/development/ichiran.html + GSI_STD: new ol.layer.Tile({ + title: '地理院 標準地図', + source: new ol.source.XYZ({ + attributions: '地理院タイル', + url: "https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png", + minZoomLevel: 2, maxZoomLevel: 18, + projection: "EPSG:3857" + }) + }), + GSI_PALE: new ol.layer.Tile({ + title: '地理院 淡色地図', + source: new ol.source.XYZ({ + attributions: '地理院タイル', + url: "https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png", + minZoomLevel: 5, maxZoomLevel: 18, + projection: "EPSG:3857" + }) + }), + GSI_ENG: new ol.layer.Tile({ + title: '地理院 English', + source: new ol.source.XYZ({ + attributions: '地理院タイル', + url: "https://cyberjapandata.gsi.go.jp/xyz/english/{z}/{x}/{y}.png", + minZoomLevel: 5, maxZoomLevel: 11, + projection: "EPSG:3857" + }) + }), + GSI_BLANK: new ol.layer.Tile({ + title: '地理院 白地図', + source: new ol.source.XYZ({ + attributions: '地理院タイル', + url: "https://cyberjapandata.gsi.go.jp/xyz/blank/{z}/{x}/{y}.png", + minZoomLevel: 5, maxZoomLevel: 14, + projection: "EPSG:3857" + }) + }), + GSI_PHOTO: new ol.layer.Tile({ + title: '地理院 写真', + source: new ol.source.XYZ({ + attributions: '地理院タイル', + url: "https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg", + minZoomLevel: 2, maxZoomLevel: 18, + projection: "EPSG:3857" + }) + }), + GSI_ORT: new ol.layer.Tile({ + title: '地理院 写真', + source: new ol.source.XYZ({ + attributions: '地理院タイル', + url: "https://cyberjapandata.gsi.go.jp/xyz/ort/{z}/{x}/{y}.jpg", + minZoomLevel: 15, maxZoomLevel: 17, + projection: "EPSG:3857" + }) + }), + GSI_RELIEF: new ol.layer.Tile({ + title: '地理院 色別標高図', + source: new ol.source.XYZ({ + attributions: '地理院タイル', + url: "https://cyberjapandata.gsi.go.jp/xyz/relief/{z}/{x}/{y}.png", + minZoomLevel: 5, maxZoomLevel: 15, + projection: "EPSG:3857" + }) + }), + GSI_AFM: new ol.layer.Tile({ + title: '地理院 活断層図', + source: new ol.source.XYZ({ + attributions: '地理院タイル', + url: "https://cyberjapandata.gsi.go.jp/xyz/afm/{z}/{x}/{y}.png", + minZoomLevel: 11, maxZoomLevel: 16, + projection: "EPSG:3857" + }) + }), + GSI_SHINSUISHIN: new ol.layer.Tile({ // 洪水浸水想定区域(想定最大規模) + title: '洪水浸水想定区域', + source: new ol.source.XYZ({ + attributions: 'ハザードマップ', + url: "https://disaportaldata.gsi.go.jp/raster/01_flood_l2_shinsuishin_data/{z}/{x}/{y}.png", + minZoomLevel: 2, maxZoomLevel: 17, + projection: "EPSG:3857" + }) + }), + GSI_SHINSUISHIN_KUNI: new ol.layer.Tile({ // 洪水浸水想定区域(想定最大規模)_国管理河川 + title: '洪水浸水想定区域_国管理河川', + source: new ol.source.XYZ({ + attributions: 'ハザードマップ', + url: "https://disaportaldata.gsi.go.jp/raster/01_flood_l2_shinsuishin_kuni_data/{z}/{x}/{y}.png", + minZoomLevel: 2, maxZoomLevel: 17, + projection: "EPSG:3857" + }) + }), + GSI_TSUNAMI: new ol.layer.Tile({ // 津波浸水想定 + title: '津波浸水想定', + source: new ol.source.XYZ({ + attributions: 'ハザードマップ', + url: "https://disaportaldata.gsi.go.jp/raster/04_tsunami_newlegend_data/{z}/{x}/{y}.png", + minZoomLevel: 2, maxZoomLevel: 17, + projection: "EPSG:3857" + }) + }), + GSI_DOSEKIRYUKEIKAIKUIKI: new ol.layer.Tile({ // 土砂災害警戒区域(土石流) + title: '土砂災害警戒区域(土石流)', + source: new ol.source.XYZ({ + attributions: 'ハザードマップ', + url: "https://disaportaldata.gsi.go.jp/raster/05_dosekiryukeikaikuiki/{z}/{x}/{y}.png", + minZoomLevel: 2, maxZoomLevel: 17, + projection: "EPSG:3857" + }) + }), + GSI_KYUKEISHAKEIKAIKUIKI: new ol.layer.Tile({ // 土砂災害警戒区域(急傾斜地の崩壊) + title: '土砂災害警戒区域(急傾斜地の崩壊)', + source: new ol.source.XYZ({ + attributions: 'ハザードマップ', + url: "https://disaportaldata.gsi.go.jp/raster/05_kyukeishakeikaikuiki/{z}/{x}/{y}.png", + minZoomLevel: 2, maxZoomLevel: 17, + projection: "EPSG:3857" + }) + }), + GSI_JISUBERIKEIKAIKUIKI: new ol.layer.Tile({ // 土砂災害警戒区域(地滑り) + title: '土砂災害警戒区域(地滑り)', + source: new ol.source.XYZ({ + attributions: 'ハザードマップ', + url: "https://disaportaldata.gsi.go.jp/raster/05_jisuberikeikaikuiki/{z}/{x}/{y}.png", + minZoomLevel: 2, maxZoomLevel: 17, + projection: "EPSG:3857" + }) + }), + GSI_DOSEKIRYUKIKENKEIRYU: new ol.layer.Tile({ // 土石流危険渓流 + title: '土石流危険渓流', + source: new ol.source.XYZ({ + attributions: 'ハザードマップ', + url: "https://disaportaldata.gsi.go.jp/raster/05_dosekiryukikenkeiryu/{z}/{x}/{y}.png", + minZoomLevel: 2, maxZoomLevel: 17, + projection: "EPSG:3857" + }) + }), + GSI_KYUKEISYACHIHOUKAI: new ol.layer.Tile({ // 急傾斜地崩壊危険箇所 + title: '急傾斜地崩壊危険箇所', + source: new ol.source.XYZ({ + attributions: 'ハザードマップ', + url: "https://disaportaldata.gsi.go.jp/raster/05_kyukeisyachihoukai/{z}/{x}/{y}.png", + minZoomLevel: 2, maxZoomLevel: 17, + projection: "EPSG:3857" + }) + }), + GSI_JISUBERIKIKENKASYO: new ol.layer.Tile({ // 地すべり危険箇所 + title: '地すべり危険箇所', + source: new ol.source.XYZ({ + attributions: 'ハザードマップ', + url: "https://disaportaldata.gsi.go.jp/raster/05_jisuberikikenkasyo/{z}/{x}/{y}.png", + minZoomLevel: 2, maxZoomLevel: 17, + projection: "EPSG:3857" + }) + }), + GSI_NADAREKIKENKASYO: new ol.layer.Tile({ // 雪崩危険箇所 + title: '雪崩危険箇所', + source: new ol.source.XYZ({ + attributions: 'ハザードマップ', + minZoomLevel: 2, maxZoomLevel: 17, + projection: "EPSG:3857" + }) + }), WIKIMEDIA: new ol.layer.Tile({ source: new ol.source.OSM({ url: "https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png" @@ -148,7 +310,16 @@ }) }); + if (options.style.text != null) { + style.setText(options.style.text(ol, options.style)); + } + return (feature, resolution) => { + var data = feature.get('data'); + if (data.style != null && data.style.context != null) { + data.style.context(ol, style, feature, resolution); + } + if (this.selected_feature === feature) { return style; } @@ -207,6 +378,24 @@ src: icon.src, scale: icon.scale })); + } else if (icon.fontawesome != null) { + if (typeof icon.fontawesome === 'string') { + icon.fontawesome = {'glyph': icon.fontawesome}; + } + const canvas = build_font_awesome_icon.call(this, icon.fontawesome); + if (canvas == null) { + return DEFAULT_MARKER; + } + var image = new ol.style.Icon(/** @type {olx.style.IconOptions} */ ({ + anchor: icon.anchor, + anchorXUnits: icon.anchorXUnits, + anchorYUnits: icon.anchorYUnits, + opacity: icon.opacity, + img: canvas, + imgSize: [canvas.width, canvas.height] + })); + } else if (vector_style != null && vector_style.image != null) { + var image = vector_style.image(ol, vector_style, this.map.getView().getResolution()); } let marker_style = build_basic_style.call(this, { image: image, @@ -220,6 +409,115 @@ return marker_style; }; + // Create a table mapping class name to unicode. + const create_fa_glyph_table = function reate_fa_glyph_table() { + let found = false; + const styleSheets = this.get_styleSheets(); + for (let i = 0; i < styleSheets.length; i++) { + const sheet = styleSheets[i]; + if (sheet && !found) { + const before = '::before'; + for (let j = 0; j < sheet.cssRules.length; j++) { + const cssRule = sheet.cssRules[j]; + if (cssRule.selectorText && cssRule.selectorText.startsWith('.fa') && cssRule.selectorText.endsWith(before)) { + const ctx = String.fromCodePoint(cssRule.style.content.replace(/'|"/g, '').charCodeAt(0)); + this.fa_glyph_table[cssRule.selectorText.slice(1).slice(0, -1 * before.length)] = ctx; + found = true; + } + } + } + } + + this.fa_marker_cache = {}; + } + + // Build a marker with Font awsome icon + const build_font_awesome_icon = function build_font_awesome_icon(fontSymbol) { + if (!Object.keys(this.fa_glyph_table).length) { + create_fa_glyph_table.call(this); + } + const glyph = fontSymbol.glyph || 'fa-star'; + let form = fontSymbol.form || 'marker'; + const size = fontSymbol.size || 16; + const fill = fontSymbol.fill || 'blue'; + const stroke = fontSymbol.stroke || 'white'; + let color = fontSymbol.color || stroke; + const strokeWidth = fontSymbol.strokeWidth || 3; + const margin = fontSymbol.margin || 0.4; + const radius = fontSymbol.radius || (size / 2) + strokeWidth + size * margin; + const unicode = this.fa_glyph_table[glyph]; + if (typeof unicode === 'undefined') { + return null; + } + + const hash = glyph + form + size + fill + stroke + color + strokeWidth + radius + unicode; + if (hash in this.fa_marker_cache) { + return this.fa_marker_cache[hash]; + } + + const canvas = window.top.document.createElement('canvas'); + canvas.width = radius * 2; + canvas.height = radius * 2; + + const context = canvas.getContext('2d'); + + switch (form) { + case 'icon': + const size2 = size + strokeWidth * 2; + context.font = `600 ${size2}px "Font Awesome 5 Free"`; + context.textAlign = 'center'; + context.textBaseline = 'middle'; + context.fillStyle = stroke; + context.fillText(unicode, radius, radius); + if (stroke == color) { + color = fill; + } + break; + case 'circle': + context.arc(radius, radius , radius - strokeWidth - 0.5, 0, 360, false); + context.fillStyle = fill; + context.fill(); + context.strokeStyle = stroke; + context.lineWidth = strokeWidth; + context.stroke(); + break; + case 'box': + const s = strokeWidth + 0.5 + context.beginPath(); + context.moveTo(s, s); + context.lineTo(radius * 2 - s, s); + context.lineTo(radius * 2 - s, radius * 2 - s); + context.lineTo(s, radius * 2 - s); + context.closePath(); + context.fillStyle = fill; + context.fill(); + context.strokeStyle = stroke; + context.lineWidth = strokeWidth; + context.stroke(); + break; + default: // marker + canvas.height = canvas.height * 1.2; + context.beginPath(); + context.arc(radius, radius, radius - strokeWidth - 0.5, 0.2 * Math.PI, 0.8 * Math.PI, true); + context.lineTo(radius, canvas.height - 0.5); + context.closePath(); + context.fillStyle = fill; + context.fill(); + context.strokeStyle = stroke; + context.lineWidth = strokeWidth; + context.stroke(); + } + + context.font = `600 ${size}px "Font Awesome 5 Free"`; + context.textAlign = 'center'; + context.textBaseline = 'middle'; + context.fillStyle = color; + context.fillText(unicode, radius, radius); + + this.fa_marker_cache[hash] = canvas; + return this.fa_marker_cache[hash]; + } + var send_visible_pois = function send_visible_pois() { if (this.visiblePoisTimeout != null) { @@ -309,7 +607,7 @@ update_ui_buttons({editing: MashupPlatform.mashup.context.get("editing")}); DEFAULT_MARKER = build_basic_style.call(this); - this.base_layer = CORE_LAYERS.OSM; + this.base_layer = CORE_LAYERS[MashupPlatform.prefs.get('maptype')]; var initialCenter = MashupPlatform.prefs.get("initialCenter").split(",").map(Number); if (initialCenter.length != 2 || !Number.isFinite(initialCenter[0]) || !Number.isFinite(initialCenter[1])) { initialCenter = [0, 0]; @@ -359,18 +657,119 @@ } }); + var layers = [ + this.base_layer, + MashupPlatform.prefs.get("useclustering") ? this.cluster_layer : this.vector_layer + ]; + + var other_layer = MashupPlatform.prefs.get('layer_swipe'); + + if (other_layer !== 'Off') { + + var swipe_value = MashupPlatform.prefs.get('swipe_value'); + if (swipe_value < 0 || swipe_value > 101) { + swipe_value = 50; + } + + other_layer = CORE_LAYERS[other_layer]; + layers.splice(1, 0, other_layer); + + if (swipe_value <= 100) { + var swipe = document.getElementById('swipe'); + swipe.classList.remove('hidden'); + swipe.value = swipe_value; + + other_layer.on('prerender', function (event) { + var ctx = event.context; + var width = ctx.canvas.width * (swipe.value / 100); + + ctx.save(); + ctx.beginPath(); + ctx.rect(width, 0, ctx.canvas.width - width, ctx.canvas.height); + ctx.clip(); + }); + + other_layer.on('postrender', function (event) { + var ctx = event.context; + ctx.restore(); + }); + + swipe.addEventListener('input', () => { + this.map.render(); + }, false); + + } else { + var imagery = other_layer; + var container = document.getElementById('map'); + var radius = 75; + document.addEventListener('keydown', (evt) => { + if (evt.which === 38) { + radius = Math.min(radius + 5, 150); + this.map.render(); + evt.preventDefault(); + } else if (evt.which === 40) { + radius = Math.max(radius - 5, 25); + this.map.render(); + evt.preventDefault(); + } + }); + + // get the pixel position with every move + var mousePosition = null; + + container.addEventListener('mousemove', (event) => { + mousePosition = this.map.getEventPixel(event); + this.map.render(); + }); + + container.addEventListener('mouseout', () => { + mousePosition = null; + this.map.render(); + }); + + // before rendering the layer, do some clipping + imagery.on('precompose', (event) => { + var ctx = event.context; + var pixelRatio = event.frameState.pixelRatio; + ctx.save(); + ctx.beginPath(); + if (mousePosition) { + // only show a circle around the mouse + ctx.arc(mousePosition[0] * pixelRatio, mousePosition[1] * pixelRatio, + radius * pixelRatio, 0, 2 * Math.PI); + ctx.lineWidth = 5 * pixelRatio; + ctx.strokeStyle = 'rgba(0,0,0,0.5)'; + ctx.stroke(); + } + ctx.clip(); + }); + + // after rendering the layer, restore the canvas context + imagery.on('postcompose', (event) => { + var ctx = event.context; + ctx.restore(); + }); + } + } + this.map = new ol.Map({ target: document.getElementById('map'), - layers: [ - this.base_layer, - MashupPlatform.prefs.get("useclustering") ? this.cluster_layer : this.vector_layer - ], + layers: layers, view: new ol.View({ center: ol.proj.transform(initialCenter, 'EPSG:4326', 'EPSG:3857'), zoom: parseInt(MashupPlatform.prefs.get('initialZoom'), 10) }) }); + if (MashupPlatform.prefs.get('overview')) { + this.map.addControl(new ol.control.OverviewMap({ + layers: [new ol.layer.Tile({source: new ol.source.OSM()})], + })); + } + if (MashupPlatform.prefs.get('scaleline')) { + this.map.addControl(new ol.control.ScaleLine()); + } + // display popup on click this.map.on('click', function (event) { var features = []; @@ -459,6 +858,11 @@ this.map.on('moveend', this.send_visible_pois_bound); this.geojsonparser = new ol.format.GeoJSON(); + + this.fa_glyph_table = {}; + this.get_styleSheets = function get_styleSheets() { + return window.top.document.styleSheets; + } }; Widget.prototype.registerPoI = function registerPoI(poi_info) { @@ -527,6 +931,18 @@ } }; + Widget.prototype.removePoI = function removePoI(poi_info) { + var feature = this.vector_source.getFeatureById(poi_info.id); + if (feature != null) { + if (feature == this.selected_feature) { + if (this.popover != null) { + this.popover.hide(); + } + } + this.vector_source.removeFeature(feature); + } + } + /** * Replace all the PoIs currently displayed on the map with the ones provided as parameters. * @@ -953,6 +1369,40 @@ return build_layer.call(this, "Tile", options, layer_info); }; + var addHeatmapLayer = function addHeatmapLayer(layer_info) { + var new_features = []; + layer_info.features.forEach(function (feature) { + var new_feature = new ol.Feature({ + geometry: new ol.geom.Point(ol.proj.transform([feature.lng, feature.lat], "EPSG:4326","EPSG:900913")) + }); + new_feature.set('weight', feature.weight / layer_info.max); + new_features.push(new_feature); + }); + var source = new ol.source.Vector({ + features: new_features + }); + var heatmap = new ol.layer.Heatmap({ + blur: layer_info.blur, + radius: layer_info.radius, + source: source + }); + return heatmap; + }; + + // 'https://raw.githubusercontent.com/openlayers/openlayers/master/examples/data/kml/2012_Earthquakes_Mag5.kml', + var addHeatmapKMLLayer = function addHeatmapKMLLayer(layer_info) { + return new ol.layer.Heatmap({ + source: new ol.source.Vector({ + url: layer_info.unlKML, + format: new ol.format.KML({ + extractStyles: false + }) + }), + blur: 15, + radius: 20 + }); + }; + var addWMTSLayer = function addWMTSLayer(layer_info) { const options = { source: new ol.source.WMTS({ @@ -1116,6 +1566,8 @@ const layer_builders = { "BingMaps": addBingMapsLayer, "CartoDB": addCartoDBLayer, + "Heatmap": addHeatmapLayer, + "HeatmapKML": addHeatmapKMLLayer, "ImageWMS": addImageWMSLayer, "ImageArcGISRest": addImageArcGISRestLayer, "ImageMapGuide": addImageMapGuideLayer, diff --git a/tests/js/OpenlayersWidgetSpec.js b/tests/js/OpenlayersWidgetSpec.js index f858311..d68afaf 100644 --- a/tests/js/OpenlayersWidgetSpec.js +++ b/tests/js/OpenlayersWidgetSpec.js @@ -11,7 +11,8 @@ const HTML_FIXTURE = '
\n' + '
\n' + - '
'; + '
\n' + + ''; const clearDocument = function clearDocument() { var elements = document.querySelectorAll('body > *:not(.jasmine_html-reporter)'); @@ -63,6 +64,11 @@ 'initialZoom': '10', 'poiZoom': 10, 'layerswidget': '', + 'maptype': 'OSM', + 'layer_swipe': 'Off', + 'swipe_value': 50, + 'overview': false, + 'scaleline': false, 'useclustering': false }, inputs: ['layerInfo'], @@ -1044,6 +1050,119 @@ }); + describe("handles the custom styles:", () => { + it("Add custom style image", () => { + widget.init(); + spyOn(widget.vector_source, 'addFeature'); + widget.registerPoI(deepFreeze({ + id: '1', + data: {}, + location: { + type: 'Point', + coordinates: [0, 0] + }, + style: { + image: function (ol, style, resolution) { + return new ol.style.Circle({ + fill: new ol.style.Fill({ + color: '#111111' + }), + radius: (1000 / resolution) * style.radius, + stroke: new ol.style.Stroke({ + color: '#222222', + width: 1 + }) + }); + }, + radius: 10, + }, + })); + expect(widget.vector_source.addFeature).toHaveBeenCalledTimes(1); + expect(widget.vector_source.addFeature).toHaveBeenCalledWith(jasmine.any(ol.Feature)); + let feature = widget.vector_source.addFeature.calls.argsFor(0)[0]; + let fstyle = feature.getStyle()(feature); + expect(fstyle.getImage().getFill().getColor()).toEqual('#111111'); + expect(fstyle.getImage().getRadius()).toEqual(65.41332273339661); + expect(fstyle.getImage().getStroke().getColor()).toEqual('#222222'); + expect(fstyle.getImage().getStroke().getWidth()).toEqual(1); + }); + + it("Add custom style text", () => { + widget.init(); + spyOn(widget.vector_source, 'addFeature'); + widget.registerPoI(deepFreeze({ + id: '1', + data: {}, + location: { + type: 'Point', + coordinates: [0, 0] + }, + style: { + text: function (ol, style) { + return new ol.style.Text({ + font: '12px serif', + text: style.value.toString(), + fill: new ol.style.Fill({ + color: '#333333' + }) + }) + }, + value: 'test', + }, + })); + expect(widget.vector_source.addFeature).toHaveBeenCalledTimes(1); + expect(widget.vector_source.addFeature).toHaveBeenCalledWith(jasmine.any(ol.Feature)); + let feature = widget.vector_source.addFeature.calls.argsFor(0)[0]; + let fstyle = feature.getStyle()(feature); + expect(fstyle.getText().getFont()).toEqual('12px serif'); + expect(fstyle.getText().getText()).toEqual('test'); + expect(fstyle.getText().getFill().getColor()).toEqual('#333333'); + }); + + it("Add custom style image with maxzoom", () => { + widget.init(); + spyOn(widget.vector_source, 'addFeature'); + widget.registerPoI(deepFreeze({ + id: '1', + data: {}, + location: { + type: 'Point', + coordinates: [0, 0] + }, + maxzoom: 13, + style: { + image: function (ol, style, resolution) { + return new ol.style.Circle({ + fill: new ol.style.Fill({ + color: '#444444' + }), + radius: (1000 / resolution) * style.radius, + stroke: new ol.style.Stroke({ + color: '#555555', + width: 2 + }) + }); + }, + radius: 10, + context: function (ol, style, feature, resolution) { + style.getImage().setRadius(20); + }, + }, + })); + + expect(widget.vector_source.addFeature).toHaveBeenCalledTimes(1); + expect(widget.vector_source.addFeature).toHaveBeenCalledWith(jasmine.any(ol.Feature)); + let feature = widget.vector_source.addFeature.calls.argsFor(0)[0]; + let fstyle = feature.getStyle()(feature); + expect(fstyle.getImage().getFill().getColor()).toEqual('#444444'); + expect(fstyle.getImage().getRadius()).toEqual(20); + expect(fstyle.getImage().getStroke().getColor()).toEqual('#555555'); + expect(fstyle.getImage().getStroke().getWidth()).toEqual(2); + + }); + + }); + describe("handles the minzoom option:", () => { const test = function (resolution, displayed) { return () => { @@ -1967,6 +2086,171 @@ }); + describe("build marker with Font Awesome icon", () => { + it("build default marker", () => { + widget.init(); + spyOn(widget.vector_source, 'addFeature'); + spyOn(widget, 'get_styleSheets').and.returnValue([ + {cssRules: [{selectorText: '.fa-star::before', style: {content: '\uf005'}}]}, + {cssRules: [{selectorText: '', style: {content: ''}}]} + ]); + widget.registerPoI(deepFreeze({ + id: '1', + data: {}, + location: { + type: 'Point', + coordinates: [0, 0] + }, + icon: { + 'fontawesome': '' + } + })); + expect(widget.vector_source.addFeature).toHaveBeenCalledTimes(1); + expect(widget.vector_source.addFeature).toHaveBeenCalledWith(jasmine.any(ol.Feature)); + }); + it("build marker with icon form", () => { + widget.init(); + spyOn(widget.vector_source, 'addFeature'); + spyOn(widget, 'get_styleSheets').and.returnValue([ + {cssRules: [{selectorText: '.fa-star::before', style: {content: '\uf005'}}]} + ]); + widget.registerPoI(deepFreeze({ + id: '1', + data: {}, + location: { + type: 'Point', + coordinates: [0, 0] + }, + icon: { + 'fontawesome': { + 'glyph': 'fa-star', + 'form': 'icon' + } + } + })); + expect(widget.vector_source.addFeature).toHaveBeenCalledTimes(1); + expect(widget.vector_source.addFeature).toHaveBeenCalledWith(jasmine.any(ol.Feature)); + }); + it("build red marker with icon form", () => { + widget.init(); + spyOn(widget.vector_source, 'addFeature'); + spyOn(widget, 'get_styleSheets').and.returnValue([ + {cssRules: [{selectorText: '.fa-star::before', style: {content: '\uf005'}}]} + ]); + widget.registerPoI(deepFreeze({ + id: '1', + data: {}, + location: { + type: 'Point', + coordinates: [0, 0] + }, + icon: { + 'fontawesome': { + 'glyph': 'fa-star', + 'form': 'icon', + 'color': 'red' + } + } + })); + expect(widget.vector_source.addFeature).toHaveBeenCalledTimes(1); + expect(widget.vector_source.addFeature).toHaveBeenCalledWith(jasmine.any(ol.Feature)); + }); + it("build marker with circle form", () => { + widget.init(); + spyOn(widget.vector_source, 'addFeature'); + spyOn(widget, 'get_styleSheets').and.returnValue([ + {cssRules: [{selectorText: '.fa-star::before', style: {content: '\uf005'}}]} + ]); + widget.registerPoI(deepFreeze({ + id: '1', + data: {}, + location: { + type: 'Point', + coordinates: [0, 0] + }, + icon: { + 'fontawesome': { + 'glyph': 'fa-star', + 'form': 'circle' + } + } + })); + expect(widget.vector_source.addFeature).toHaveBeenCalledTimes(1); + expect(widget.vector_source.addFeature).toHaveBeenCalledWith(jasmine.any(ol.Feature)); + }); + it("build marker with box form", () => { + widget.init(); + spyOn(widget.vector_source, 'addFeature'); + spyOn(widget, 'get_styleSheets').and.returnValue([ + {cssRules: [{selectorText: '.fa-star::before', style: {content: '\uf005'}}]} + ]); + widget.registerPoI(deepFreeze({ + id: '1', + data: {}, + location: { + type: 'Point', + coordinates: [0, 0] + }, + icon: { + 'fontawesome': { + 'glyph': 'fa-star', + 'form': 'box' + } + } + })); + expect(widget.vector_source.addFeature).toHaveBeenCalledTimes(1); + expect(widget.vector_source.addFeature).toHaveBeenCalledWith(jasmine.any(ol.Feature)); + }); + it("should use icon cache", () => { + widget.init(); + spyOn(widget.vector_source, 'addFeature'); + spyOn(widget, 'get_styleSheets').and.returnValue([ + {cssRules: [{selectorText: '.fa-star::before', style: {content: '\uf005'}}]} + ]); + widget.registerPoI(deepFreeze({ + id: '1', + data: {}, + location: { + type: 'Point', + coordinates: [0, 0] + }, + icon: { + 'fontawesome': 'fa-star' + } + })); + widget.registerPoI(deepFreeze({ + id: '2', + data: {}, + location: { + type: 'Point', + coordinates: [0, 0] + }, + icon: { + 'fontawesome': 'fa-star' + } + })); + expect(widget.vector_source.addFeature).toHaveBeenCalledTimes(2); + expect(widget.vector_source.addFeature).toHaveBeenCalledWith(jasmine.any(ol.Feature)); + }); + it("fallback when glyph not found", () => { + widget.init(); + spyOn(widget.vector_source, 'addFeature'); + widget.registerPoI(deepFreeze({ + id: '1', + data: {}, + location: { + type: 'Point', + coordinates: [0, 0] + }, + icon: { + 'fontawesome': 'fa-star' + } + })); + expect(widget.vector_source.addFeature).toHaveBeenCalledTimes(1); + expect(widget.vector_source.addFeature).toHaveBeenCalledWith(jasmine.any(ol.Feature)); + }); + }); + }); })();