clustering is too slow, 7000 points take 2 min to load - cluster-analysis

We faced a problem with too slow loading of clusters on HERE map. We have just 7000 points. In the example at HERE's tutorial clusting of more than 10.000 airport points works smoothly. It looks like the most time takes getNoisePresentation method (see below).
We are really struggling and will appreaicte your tips and help of how to overcome this problem. Thank you in advance.
Here is a clustering code:
const clusterOrigin = new H.clustering.Provider(this[typeFreights].originPoints, {
clusteringOptions: clusteringOptions,
theme: {
getClusterPresentation(cluster) {
let counts = 0
cluster.forEachDataPoint(point => counts += point.getData().counts)
const weight = cluster.getWeight()
const size = countScale(weight) * pixelRatio
const viewBox = [-size, -size, size * 2, size * 2]
const marker = svg
.replace(/\{counts\}/g, counts)
.replace(/\{viewBox\}/g, viewBox)
.replace(/\{size\}/g, size)
.replace(/\{fill\}/g, "#1d6eb6")
const clusterMarker = new H.map.Marker(cluster.getPosition(), {
icon: new H.map.Icon(marker, {
size: { w: size, h: size },
}),
min: cluster.getMinZoom(),
max: cluster.getMaxZoom(),
})
return clusterMarker
},
getNoisePresentation(noisePoint) {
const data = noisePoint.getData()
const weight = noisePoint.getWeight()
const size = countScale(weight) * pixelRatio
const viewBox = [-size, -size, size * 2, size * 2]
const marker = svg
.replace(/\{counts\}/g, data.counts)
.replace(/\{viewBox\}/g, viewBox)
.replace(/\{size\}/g, 30)
.replace(/\{fill\}/g, "#1d6eb6")
const noiseMarker = new H.map.Marker(noisePoint.getPosition(), {
min: noisePoint.getMinZoom(),
icon: new H.map.Icon(marker),
})
return noiseMarker
},
},
})
const clusterDestination = new H.clustering.Provider(this[typeFreights].destionationPoints, {
clusteringOptions: clusteringOptions,
theme: {
getClusterPresentation(cluster) {
let counts = 0
cluster.forEachDataPoint(point => counts += point.getData().counts)
const weight = cluster.getWeight()
const size = countScale(weight) * pixelRatio
const viewBox = [-size, -size, size * 2, size * 2]
const marker = svg
.replace(/\{counts\}/g, counts)
.replace(/\{viewBox\}/g, viewBox)
.replace(/\{size\}/g, size)
.replace(/\{fill\}/g, "#fe910c")
const clusterMarker = new H.map.Marker(cluster.getPosition(), {
icon: new H.map.Icon(marker, {
size: { w: size, h: size },
}),
min: cluster.getMinZoom(),
max: cluster.getMaxZoom(),
})
return clusterMarker
},
getNoisePresentation(noisePoint) {
const data = noisePoint.getData()
const weight = noisePoint.getWeight()
const size = countScale(weight) * pixelRatio
const viewBox = [-size, -size, size * 2, size * 2]
const marker = svg
.replace(/\{counts\}/g, data.counts)
.replace(/\{viewBox\}/g, viewBox)
.replace(/\{size\}/g, 30)
.replace(/\{fill\}/g, "#fe910c")
const noiseMarker = new H.map.Marker(noisePoint.getPosition(), {
min: noisePoint.getMinZoom(),
icon: new H.map.Icon(marker),
})
return noiseMarker
},
},
})

It looks like the major problem is that you do not cache icons and thus creating is an unnecessary burden on memory and CPU.
In methods "getClusterPresentation" and "getNoisePresentation" there are calls to H.new.Icon and there is no no caching and reuse of the already created Icons.
Which means in the worst case scenario it will create 7000 icons even if these icons look the same - see
Best Practices -> Icon Reuse in the Developer's Guide (https://developer.here.com/documentation/maps/topics/best-practices.html)
Hope this helps.

Related

Donut chart with custom tooltip using Chartist in React

I am creating a donut chart using Chartist but unable to add a custom tooltip with resize slice on the mouse hover event. Chartist "Draw" method is rerender on changing the position of mouse on hover of the chart
I pasted my code as below:
/* eslint-disable no-unused-vars */
import React, {useState} from 'react'
import PropTypes from 'prop-types'
import ChartistGraph from 'react-chartist'
import Chartist from 'chartist'
import styles from './MyChart.scss'
import {formatNumber} from '../../../../utils/formatter'
import MyChartInfo from './MyChartInfo'
function MyChart ({ dataSeries, getDetailsTable }) {
const [changePercent, setChangePercent] = useState(0)
const [showToolTip, setShowToolTip] = useState(false)
const [myChartInfoDetails, setMyChartInfoDetails] = useState({sectorName: '', changePercent: ''})
const [toolTipPosition, setToolTipPosition] = useState({})
function setToolTip () {
const PopOverData = [myChartInfoDetails.sectorName || '', `% Return: ${myChartInfoDetails.changePercent || ''}`, '(Click to view top & worst performing equities)']
return (<MyChartInfo dataTable={PopOverData} style={toolTipPosition} />)
}
let pieOptions = {
width: '520px',
height: '520px',
donut: true,
donutWidth: 150,
donutSolid: false,
startAngle: 270,
showLabel: true,
chartPadding: 60,
labelOffset: 105,
labelDirection: 'explode',
labelInterpolationFnc: function (value) {
return value
}
}
let pieData = {
series: dataSeries && dataSeries.length > 0 ? dataSeries.map(item => Math.abs(item.value)) : [100],
labels: dataSeries && dataSeries.length > 0 ? dataSeries.map(item => item.name.concat('<br>', formatNumber(item.value, {precision: 2, negSign: true, posSign: true}))) : ['']
}
// eslint-disable-next-line no-unused-vars
const listner = {
created: (chart) => {
const chartPie = document.querySelectorAll('.ct-chart-donut .ct-slice-donut')
chartPie && chartPie.forEach((pie) => {
pie.addEventListener('mouseenter', (e) => {
e.stopPropagation()
pie.setAttribute('style', 'stroke-width: 170px;')
console.log('hover aa ', changePercent, Math.abs(pie.getAttribute('ct:value')))
setChangePercent(Math.abs(pie.getAttribute('ct:value')))
let topPosition = e.layerY + 30
let leftPosition = e.layerX + 30
setToolTipPosition({top: topPosition, left: leftPosition})
const myChartData = dataSeries.find(sector => Math.abs(sector.value) === Math.abs(pie.getAttribute('ct:value')))
setSectorDetails({sectorName: myChartData.name, changePercent: myChartData.value})
setShowToolTip(true)
})
pie.addEventListener('mouseleave', (e) => {
pie.setAttribute('style', 'stroke-width: 150px;')
setShowToolTip(false)
})
pie.addEventListener('click', (e) => {
const Id = dataSeries.find(sector => Math.abs(sector.value) === Math.abs(pie.getAttribute('ct:value'))).id.toString()
console.log('click ', Id, pie.getAttribute('ct:value'))
getDetailsTable(Id)
})
})
},
draw: (data) => {
// console.log('data', data)
if (data.type === 'slice') {
// Get the total path length in order to use for dash array animation
var pathLength = data.element._node.getTotalLength()
// Set a dasharray that matches the path length as prerequisite to animate dashoffset
data.element.attr({
'stroke-dasharray': pathLength + 'px ' + pathLength + 'px'
})
let noOfNonZeros = dataSeries.length > 0 && dataSeries.filter(e => e.value > 0).length
// Create animation definition while also assigning an ID to the animation for later sync usage
var animationDefinition = {
'stroke-dashoffset': {
id: 'anim' + data.index,
dur: 1,
from: -pathLength + 'px',
to: noOfNonZeros > 1 ? '2px' : '0px',
easing: Chartist.Svg.Easing.easeOutQuint,
// We need to use `fill: 'freeze'` otherwise our animation will fall back to initial (not visible)
fill: 'freeze'
}
}
// If this was not the first slice, we need to time the animation so that it uses the end sync event of the previous animation
if (data.index !== 0) {
animationDefinition['stroke-dashoffset'].begin = 'anim' + (data.index - 1) + '.end'
}
// We need to set an initial value before the animation starts as we are not in guided mode which would do that for us
data.element.attr({
'stroke-dashoffset': -pathLength + 'px'
})
// We can't use guided mode as the animations need to rely on setting begin manually
// See http://gionkunz.github.io/chartist-js/api-documentation.html#chartistsvg-function-animate
data.element.animate(animationDefinition, false)
if (dataSeries.length === 0) {
data.element._node.setAttribute('data-value', 'no-composition')
}
}
if (data.type === 'label') {
let textHtml = ['<p>', data.text, '</p>'].join('')
let multilineText = Chartist.Svg('svg').foreignObject(
textHtml,
{
style: 'overflow: visible;',
x: data.x - 30,
y: data.y - 30,
width: 90,
height: 30
},
'ct-label'
)
data.element.replace(multilineText)
}
}
}
return (
<div data-testid='gPieChart' id='performance_chart'>
{<ChartistGraph
data={pieData}
options={pieOptions}
type='Pie'
style={{ width: '100%', height: '100%' }}
className={styles.MyChart}
listener={listner}
/>
}
{showToolTip && setToolTip()}
</div>
)
}
MyChart.propTypes = {
dataSeries: PropTypes.array,
getDetailsTable: PropTypes.func
}
export default MyChart
Output
I want to add a custom tooltip with resize slice functionality on mouse hover event

How to create colision detection in a SDK matterport

I am using three.js to create a simple 3d Object ( CylinderGeometry) in the Matterport.
So i want to make a colision detection with a wall using SDK for the 3d Obj
So there is the code for the cylinder implementation
const THREE = this.context.three;
const textureLoader = new THREE.TextureLoader();
const geometry = new THREE.CylinderGeometry( 0.09, 0.09, 5, 5 );
geometry.rotateZ(-Math.PI * 0.5);
const material = new THREE.MeshBasicMaterial( {color: this.finalColor } );
const cylinder = new THREE.Mesh( geometry, material );
cylinder.name = "poiCylinder";
if (!this.inputs.sprite) {
return;
}
if (!this.inputs.sprite.startsWith('/assets/')) {
textureLoader.crossOrigin = 'Anonymous';
}
let map;
if (this.inputs.sprite.indexOf('.gif') !== -1) {
map = new GifLoader().load(this.inputs.sprite);
} else {
map = textureLoader.load(this.inputs.sprite);
}
map.minFilter = THREE.LinearFilter;
map.wrapS = map.wrapT = THREE.ClampToEdgeWrapping;
this.material = new THREE.SpriteMaterial( { map, color: this.inputs.color, fog: false }
);
this.material.alphaTest = 0.5;
this.material.map.encoding = THREE.sRGBEncoding;
this.sprite = new THREE.Sprite( this.material );
this.sprite.add(cylinder);
this.onObjectReady(this.sprite, true);

How to draw SVG polygon on openStreetMap programitically in flutter

Hello I want to use this api nominatim.org to find svg shape and latLng coordiante of one address.
I called this api in flutter and try to show it by Polygon in my flutter code in open street map
this is my code
late PolyEditor polyEditor;
List<Polygon> polygons = [];
var testPolygon = Polygon(
color: Colors.black.withOpacity(0.3),
points: [],
borderColor: Colors.black,
isFilled: true,
borderStrokeWidth: 1.0);
#override
void initState() {
super.initState();
polyEditor = PolyEditor(
addClosePathMarker: true,
points: testPolygon.points,
pointIcon: const Icon(
Icons.lens,
size: 15,
color: Colors.black,
),
intermediateIcon: const Icon(Icons.lens, size: 15, color: Colors.black),
callbackRefresh: () => {setState(() {})},
);
polygons.add(testPolygon);
}
SearchController searchController = Get.put(SearchController());
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
alignment: Alignment.center,
children: [
Center(
child: FlutterMap(
options: MapOptions(
allowPanningOnScrollingParent: false,
onTap: (_, ll) {
print(ll);
polyEditor.add(testPolygon.points, ll);
},
plugins: [
DragMarkerPlugin(),
],
center: LatLng(32.5231, 51.6765),
zoom: 9.4,
),
layers: [
TileLayerOptions(
urlTemplate:
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: ['a', 'b', 'c']),
PolygonLayerOptions(polygons: polygons),
DragMarkerPluginOptions(markers: polyEditor.edit()),
],
),
),
I called this api and tried to show this svg as a polygon
"svg": "M 13.397511 -52.517283599999999 L 13.397829400000001 -52.517299800000004
13.398131599999999 -52.517315099999998 13.398159400000001 -52.517112099999999 13.3975388
-52.517080700000001 Z",
but I dont know how to convert this svg string of coordinate to Polygon and show it on map when called this api and recieve this svg
I used this plugin
import 'package:flutter_map_line_editor/polyeditor.dart';
the result should be like this
see this picture
You're facing several problems:
The retrieved svg data from nominatim is not a polygon but a path
Besides it describes a single point i.e. it doesn't have any height or width.
Quite likely, the polyeditor expects a proper polygon based on an array of geodata based coordinates.
Change nominatim query to get polygon coordinates
You could change the query to something like this:
https://nominatim.openstreetmap.org/search?q=London&format=json&polygon_geojson=1
Most address search queries won't return a polygon unless they are a sight or public building like "London+Downing+street+10".
If you're searching for a city, county, state, district or sight, the json response will include polygon coordinates describing the border of the queried region.
polygon_geojson=1 parameter will return an array polygon coordinates that could be displayed on you map.
Unfortunately, you need to change the order of coordinates to use them in leaflet since geojson will return [lon, lng] instead of [lat, lon]
Js example fetching polygon from nominatim
You might translate this example to work with flutter.
function initMap(lat, lon, zoom, coords = [], bBox = []) {
var map = L.map("map").setView([lat, lon], zoom);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: "© OpenStreetMap"
}).addTo(map);
var marker = L.marker([lat, lon]).addTo(map);
if (coords.length) {
/**
* change coordinate order from [lon, lat] to [lat, lon]
* and reduce number of polygon vertices to 64
*/
let polygonPoints = getGeoJsonPoly(coords, 64);
var polygon = L.polygon(polygonPoints).addTo(map);
}
var southWest = new L.LatLng(bBox[0], bBox[2]),
northEast = new L.LatLng(bBox[1], bBox[3]),
bounds = new L.LatLngBounds(southWest, northEast);
map.fitBounds(bounds);
}
let query = "City-of-London";
//query = "London+Downing+street+10";
let url = `https://nominatim.openstreetmap.org/search?q=${query}&format=json&polygon_geojson=1`;
fetchJson(url);
function fetchJson(url) {
fetch(url)
.then((response) => response.json())
.then(function(data) {
let result = data[0];
let type = result.osm_type;
if (type != 'relation') {
result = data[1];
}
let [lat, lon] = [result.lat, result.lon];
let coords = result["geojson"]["coordinates"][0];
let bBox = result.boundingbox ? result.boundingbox : [];
initMap(lat, lon, 10, coords, bBox);
})
.catch(function(error) {
console.error(error);
});
}
function getGeoJsonPoly(coords, vertices = 0) {
let coordsL = coords.length;
let step = vertices != 0 ? Math.ceil(coordsL / vertices) : 1;
let polygonPoints = [];
for (let i = 0; i < coordsL; i += step) {
let p = coords[i];
let [lat, lon] = [p[1], p[0]];
polygonPoints.push([lat, lon]);
}
return polygonPoints;
}
#map {
height: 90vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.8.0/leaflet.js"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.8.0/dist/leaflet.css" />
<h3>Query: London+Downing+street+10</h3>
<div id="map"></div>
Optional: reducing polygon vertices
For better performance you might need to reduce the (usually huge) number of vertices.
While looping through the geoJson coordinate array you might drop coordinates like so:
/**
* change coordinate order from [lon, lat] to [lat, lon]
* and reduce number of polygon vertices to 64
*/
function getGeoJsonPoly(coords, vertices=0, round=0){
let coordsL = coords.length;
let step = vertices!=0 ? Math.ceil(coordsL / vertices) : 1;
let polygonPoints = [];
for (let i = 0; i < coordsL; i += step) {
let p = coords[i];
let [lat, lon] = [p[1], p[0]];
if(round>0){
[lat, lon] = [+lat.toFixed(round), +lat.toFixed(round)]
}
polygonPoints.push([lat, lon]);
}
return polygonPoints;
}

Changing the color in heatmap in eCharts

Is there any way to change the default colors in a calendar-based heatmap? The default heatmap runs from shades of 'yellow' to 'red', based on the value. I want to change the colors so that the color runs from 'red' to 'green'.
This is the default color scheme
With the propriety "inRange" you can change the color variation of the values.
function getVirtulData(year) {
year = year || '2017';
var date = +echarts.number.parseDate(year + '-01-01');
var end = +echarts.number.parseDate(year + '-12-31');
var dayTime = 3600 * 24 * 1000;
var data = [];
for (var time = date; time <= end; time += dayTime) {
data.push([
echarts.format.formatTime('yyyy-MM-dd', time),
Math.floor(Math.random() * 1000)
]);
}
return data;
}
option = {
visualMap: {
min: 0,
max: 1000,
inRange : {
color: ['#DD2000', '#009000' ] //From smaller to bigger value ->
}
},
calendar: {
range: '2017'
},
series: {
type: 'heatmap',
coordinateSystem: 'calendar',
data: getVirtulData(2017)
}
};
You can also use in visualMap
pieces: [
{min: 0, max: 0.5, color: 'red'},
{min: 0.5, max: 1, color: 'green'},
],
to customize even more

Leaflet playback place marker

I am using the playback plugin and i am using it on an image overlay.
https://github.com/hallahan/LeafletPlayback
I need to scale the floor map before placing the marker. with the plugin the marker is placed some where outside of the floor map.
I am able to solve the issue for GPS tracking, where i have written a function to scale the map and place the marker inside pointToLayer method of layer property.
I want to do the same for marker too. any help is appreciated.
const playbackOptions = {
playControl: true,
dateControl: true,
orientIcons: true,
fadeMarkersWhenStale: true,
// layer and marker options
layer: {
pointToLayer(featureData, latlng) {
const { lat, lng } = latlng;
let result = {};
if (featureData && featureData.properties && featureData.properties.path_options) {
result = featureData.properties.path_options;
}
if (!result.radius) {
result.radius = 5;
}
const scaleX = width / details.width;
const scaleY = height / details.length;
const m = {
x: lat * scaleX,
y: lng * scaleY,
};
const iconCls = 'asset-icon';
const item = L.marker(self.map.unproject([m.x, m.y], self.map.getMaxZoom()), {
icon: makeMarker(iconCls, 0),
opacity: 0.9,
type: 'asset',
lat,
lng,
});
item.bindTooltip(`<p>${lat}, ${lng}`, { className: 'asset-label', offset: [0, 0] });
return item;
}
},
marker: {
getPopup(featureData) {
let result = '';
if (featureData && featureData.properties && featureData.properties.title) {
result = featureData.properties.title;
}
return result;
}
}
};
If you retrieve actual GPS coordinates, it would probably be easier to actually do the reverse, i.e. to georeference your image overlay once for good, instead of trying to fiddle with the geographic coordinates of each of the feature you try to show relatively to your image.