using System; using System.Collections.Generic; using UnityEngine; using Random = UnityEngine.Random; public class Clustering3DMarkers : MonoBehaviour { public static Clustering3DMarkers instance; public int countMarkers = 50000; public GameObject prefab; public GameObject groupPrefab; private static Cluster rootCluster; private static OnlineMaps map; private static OnlineMapsControlBase3D control; private static OnlineMapsProjection projection; private int tileX; private int tileY; private List markers; private void GenerateMarkers() { rootCluster = new Cluster(2); for (int i = 0; i < countMarkers; i++) { double longitude = Random.Range(-180f, 180f); double latitude = Random.Range(-90f, 90f); OnlineMapsMarker3D marker = new OnlineMapsMarker3D(); marker.SetPosition(longitude, latitude); marker.label = "Marker " + i; marker.prefab = prefab; marker.scale = 50; rootCluster.Add(marker); } } private void GetCorners(out double tlx, out double tly, out double brx, out double bry) { int countX = map.width / OnlineMapsUtils.tileSize; int countY = map.height / OnlineMapsUtils.tileSize; double lng, lat; map.GetPosition(out lng, out lat); int zoom = map.zoom; projection.CoordinatesToTile(lng, lat, zoom, out tlx, out tly); float hcx = countX / 2f + 1; float hcy = countY / 2f + 1; brx = tlx + hcx; bry = tly + hcy; tlx -= hcx; tly -= hcy; projection.TileToCoordinates(tlx, tly, zoom, out tlx, out tly); projection.TileToCoordinates(brx, bry, zoom, out brx, out bry); } private void OnChangePosition() { double tx, ty; map.GetTilePosition(out tx, out ty); if (tileX != (int)tx || tileY != (int)ty) UpdateMarkers(); } private void OnEnable() { instance = this; } private void Start() { //Profiler.maxNumberOfSamplesPerFrame = 8000000; map = OnlineMaps.instance; control = OnlineMapsControlBase3D.instance; projection = map.projection; GenerateMarkers(); UpdateMarkers(); map.OnChangePosition += OnChangePosition; map.OnChangeZoom += UpdateMarkers; } private void UpdateMarkers() { double tlx, tly, brx, bry; GetCorners(out tlx, out tly, out brx, out bry); if (markers == null) markers = new List(); rootCluster.GetMarkers(tlx, tly, brx, bry, ref markers); OnlineMapsMarker3D[] prevMarkers = control.markers3D; OnlineMapsMarker3D[] newMarkers = markers.ToArray(); markers.Clear(); for (int i = 0; i < prevMarkers.Length; i++) { OnlineMapsMarker3D m = prevMarkers[i]; bool isExist = false; for (int j = 0; j < newMarkers.Length; j++) { OnlineMapsMarker3D m2 = newMarkers[j]; if (m == m2) { isExist = true; break; } } if (!isExist) { OnlineMapsUtils.DestroyImmediate(m.instance); m.instance = null; } } control.markers3D = newMarkers; double tx, ty; map.GetTilePosition(out tx, out ty); tileX = (int) tx; tileY = (int) ty; } internal class Cluster: ClusterItem { public ClusterItem[] childs; public int count; public int capacity; private int zoom; private int totalCount; public Cluster(int zoom) { childs = new ClusterItem[2]; capacity = 2; this.zoom = zoom; } public Cluster(int zoom, MarkerWrapper marker1, MarkerWrapper marker2): this(zoom) { totalCount = 2; marker1.GetTilePosition(zoom, out tileX, out tileY); projection.TileToCoordinates(tileX + 0.5, tileY + 0.5, zoom, out longitude, out latitude); if (zoom < OnlineMaps.MAXZOOM) { int mx1, my1, mx2, my2; marker1.GetTilePosition(zoom, out mx1, out my1); marker2.GetTilePosition(zoom, out mx2, out my2); if (mx1 == mx2 && my1 == my2) { AddChild(new Cluster(zoom + 1, marker1, marker2)); } else { AddChild(marker1); AddChild(marker2); } } else { AddChild(marker1); AddChild(marker2); } } public void Add(OnlineMapsMarker3D marker) { Add(new MarkerWrapper(marker)); } public void Add(MarkerWrapper marker) { totalCount++; if (zoom < OnlineMaps.MAXZOOM) { int mx, my; int z = zoom + 1; marker.GetTilePosition(z, out mx, out my); for (int i = 0; i < count; i++) { ClusterItem item = childs[i]; if (item.CompareTiles(z, mx, my)) { if (item is Cluster) (item as Cluster).Add(marker); else childs[i] = new Cluster(z, item as MarkerWrapper, marker); return; } } AddChild(marker); } else AddChild(marker); } private void AddChild(ClusterItem child) { if (count == capacity) { capacity *= 2; Array.Resize(ref childs, capacity); } childs[count++] = child; } public override bool CompareTiles(int z, int tx, int ty) { return tx == tileX && ty == tileY; } public void GetMarkers(double tlx, double tly, double brx, double bry, ref List markers) { TryGetMarkers(map.zoom, tlx, tly, brx, bry, ref markers); } public override void GetMarkers(int z, double tlx, double tly, double brx, double bry, ref List markers) { if (InRange(tlx, tly, brx, bry)) TryGetMarkers(z, tlx, tly, brx, bry, ref markers); } public bool InRange(double tlx, double tly, double brx, double bry) { double tx1, ty1, tx2, ty2; projection.CoordinatesToTile(tlx, tly, zoom, out tx1, out ty1); projection.CoordinatesToTile(brx, bry, zoom, out tx2, out ty2); int itx1 = (int)tx1; int itx2 = (int)tx2; int ity1 = (int)ty1; int ity2 = (int)ty2; if (itx1 > itx2) { int maxX = 1 << zoom; itx2 += maxX; if (itx2 - tileX > maxX) { itx1 -= maxX; itx2 -= maxX; } } return itx1 <= tileX && itx2 >= tileX && ity1 <= tileY && ity2 >= tileY; } private void TryGetMarkers(int z, double tlx, double tly, double brx, double bry, ref List markers) { if (zoom < z) { for (int i = 0; i < count; i++) childs[i].GetMarkers(z, tlx, tly, brx, bry, ref markers); } else { if (markerRef == null) { markerRef = new OnlineMapsMarker3D(); markerRef.SetPosition(longitude, latitude); markerRef.prefab = instance.groupPrefab; markerRef.label = "Group (childs: " + totalCount + ")"; markerRef.scale = 50; } markers.Add(markerRef); } } } internal class MarkerWrapper: ClusterItem { private double mx; private double my; public MarkerWrapper(OnlineMapsMarker3D marker) { markerRef = marker; marker.GetPosition(out longitude, out latitude); mx = longitude; my = latitude; OnlineMapsUtils.LatLongToMercat(ref mx, ref my); } public override bool CompareTiles(int zoom, int tx, int ty) { int max = 1 << zoom; double tx1 = mx * max; double ty1 = my * max; return tx == (int) tx1 && ty == (int) ty1; } public override void GetMarkers(int z, double tlx, double tly, double brx, double bry, ref List markers) { if (InRange(tlx, tly, brx, bry)) markers.Add(markerRef); } public void GetTilePosition(int z, out int tx, out int ty) { int max = 1 << z; tx = (int) (mx * max); ty = (int) (my * max); } public bool InRange(double tlx, double tly, double brx, double bry) { if (tlx > brx) { brx += 360; if (brx - longitude > 360) { tlx -= 360; brx -= 360; } } return tlx <= longitude && brx >= longitude && tly >= latitude && bry <= latitude; } } internal abstract class ClusterItem { protected int tileX; protected int tileY; protected double longitude; protected double latitude; protected OnlineMapsMarker3D markerRef; public abstract bool CompareTiles(int zoom, int tx, int ty); public abstract void GetMarkers(int z, double tlx, double tly, double brx, double bry, ref List markers); } }