public static string AgentPath(MapAgentPath path, bool addBoundingBoxex = true) { var result = new GeoJsonMultiLine(); for(int i = 0; i < path.Points.Count - 2; i++) { var p0 = path.Points[i]; var p1 = path.Points[i + 1]; var segment = new List<List<double>> { new List<double> { p0.Lon, p0.Lat }, new List<double> { p1.Lon, p1.Lat } }; result.Coordinates.Add(segment); } if (addBoundingBoxex) { foreach (var tileId in path.TileIds) { var bbox = new List<List<double>>(); var (lon0, lat0, lon1, lat1) = TileMath.GetTileBounds(tileId, WorldGraph.Zoom); bbox.Add(new List<double> { lon0, lat0 }); bbox.Add(new List<double> { lon1, lat0 }); bbox.Add(new List<double> { lon1, lat1 }); bbox.Add(new List<double> { lon0, lat1 }); bbox.Add(new List<double> { lon0, lat0 }); result.Coordinates.Add(bbox); } } return JsonSerializer.Serialize(result, SerializerOptions); }
static (int x, int y) GetLayerGlobalPixelPos(GeoCoord p, int zoom) { var(tileXAbs, tileYAbs) = TileMath.WorldToTilePos(p.Lon, p.Lat, zoom); int pixelX = (int)(tileXAbs * 256); int pixelY = (int)(tileYAbs * 256); return(pixelX, pixelY); }
public PointF LatLongToPointF(Coordinate coord) { var globalPx = TileMath.LocationToGlobalPixel(coord, _zoom); globalPx.X -= _topLeftGlobalPixel.X; globalPx.Y -= _topLeftGlobalPixel.Y; return(globalPx); }
public ViewInfo(int width, int height, Coordinate topLeftCorner, double zoom) { _width = width; _height = height; _zoom = zoom; _topLeftGlobalPixel = TileMath.LocationToGlobalPixel(topLeftCorner, zoom); }
void AddTile(WayTile tile) { if (_tiles.Any(t => t.Id == tile.Id)) { return; } _tiles.Add(tile); var(lon0, lat0, lon1, lat1) = TileMath.GetTileBounds(tile.Id, Zoom); foreach (var node in tile.Nodes) { node.TileId = tile.Id; node.VisitCount = 0; if (_nodeLut.TryGetValue(node.Id, out WayTileNode existingNode)) { // It is possible that both nodes are inside their own tile if they are exactly on the border. Which one wins doesn't matter. var insideNode = existingNode.Inside.HasValue ? existingNode : node; var outsideNode = existingNode.Inside.HasValue ? node : existingNode; // Node id already exists. This is a link between two tiles. The existing node should be outside its tile // and the new one should be inside. Otherwise something is wrong. // 1) Let the node inside its own tile inherit the connections of the other var tileLinkConnections = outsideNode.Conn.Where(id => insideNode.Conn.Any(idOther => idOther == id)).ToList(); insideNode.Conn.AddRange(tileLinkConnections); // 2) Overwrite the old one with the new one in the LUT. _nodeLut[node.Id] = insideNode; } else { _nodeLut[node.Id] = node; } } var outside = _nodeLut.Values.Where(v => !v.Inside.HasValue).ToList(); int loaded = 0; int notLoaded = 0; foreach (var n in outside) { if (!n.Inside.HasValue) { long tileId = TileMath.GetTileId(n.Point.Lon, n.Point.Lat, Zoom); if (_tiles.Any(t => t.Id == tileId)) { loaded++; } else { notLoaded++; } } // all outside nodes should be in tiles NOT loaded. } }
/// <summary> /// Calcualtes the approximate number of tiles within a bounding box between a set of zoom ranges. /// </summary> /// <param name="bounds">Bounding box of to focus on.</param> /// <param name="minZoom">Minimium zoom level range.</param> /// <param name="maxZoom">Maximium zoon level range.</param> /// <returns>An approximate number of tiles in this area betwen the specified zoom range.</returns> private long CalculateTileCount(LocationRect bounds, int minZoom, int maxZoom) { long numTiles = 0; int tlX, tlY, brX, brY; for (int z = minZoom; z <= maxZoom; z++) { TileMath.LocationToTileXY(bounds.North, bounds.West, z, out tlX, out tlY); TileMath.LocationToTileXY(bounds.South, bounds.East, z, out brX, out brY); numTiles += (brY - tlY + 1) * (brX - tlX + 1); } return(numTiles); }
public async Task <WayTile> GetTile(long tileId, int zoom) { var bbox = TileMath.GetTileBounds(tileId, zoom); string dbgMsg = $"TileId {tileId}, zoom: {zoom}, bbox: ({bbox.lon0}, {bbox.lat0}, {bbox.lon1}, {bbox.lat1})"; _logger.LogInformation($"Loading: {dbgMsg}"); // https://wiki.openstreetmap.org/wiki/Bounding_Box // The order of values in the bounding box used by Overpass API is (South, West, North, East): // minimum latitude, minimum longitude, maximum latitude, maximum longitude string cmd = $"[out: json];way(if:is_tag(\"highway\") || is_tag(\"route\"))({NumberStr(bbox.lat1)}, {NumberStr(bbox.lon0)}, {NumberStr(bbox.lat0)}, {NumberStr(bbox.lon1)});out qt;node(w);out skel qt;"; var client = new HttpClient(); var content = new StringContent(cmd); OsmResponse osmResponse = null; string body = ""; HttpResponseMessage httpRes = null; int attempt = 0; while (true) { try { var sw = Stopwatch.StartNew(); httpRes = await client.PostAsync(_currentUrl, content); body = await httpRes.Content.ReadAsStringAsync(); long elapsed = sw.ElapsedMilliseconds; osmResponse = JsonSerializer.Deserialize <OsmResponse>(body, SerializerOptions); _logger.LogInformation($"Loaded (ms: {elapsed}, chars: {body.Length}, elements: {osmResponse.Elements.Count}): {dbgMsg}, url: {_currentUrl}"); break; } catch (Exception e) { if (++attempt > 3) { _logger.LogWarning($"Giving up getting tile: {dbgMsg}, exception: {e}"); return(null); } // Try switching URL on errors _currentUrl = _currentUrl == UrlPrimary ? UrlSecondary : UrlPrimary; _logger.LogInformation($"Retrying tile: {dbgMsg}, url: {_currentUrl}, exception: {e}"); await Task.Delay(200); } } var osmNodes = osmResponse.Elements.Where(e => e.Type == "node").ToList(); var osmWays = osmResponse.Elements.Where(e => e.Type == "way").ToList(); var result = new WayTile(); result.Id = tileId; var nodeLut = new Dictionary <long, WayTileNode>(); // First create all nodes... foreach (var osmNode in osmNodes) { var node = new WayTileNode(); node.Id = osmNode.Id; node.Point = new GeoCoord(osmNode.Lon, osmNode.Lat); node.Inside = TileMath.IsInsideBounds(osmNode.Lon, osmNode.Lat, bbox) ? (byte?)1 : null; nodeLut[osmNode.Id] = node; result.Nodes.Add(node); } // ...then add connections between them foreach (var osmWay in osmWays) { int nodeCount = osmWay.Nodes.Count; for (int i = 0; i < nodeCount - 1; ++i) { var wayPointA = osmWay.Nodes[i]; var wayPointB = osmWay.Nodes[i + 1]; nodeLut.TryGetValue(wayPointA, out WayTileNode nodeA); nodeLut.TryGetValue(wayPointB, out WayTileNode nodeB); if (nodeA != null && nodeB != null) { // Skip connection if both nodes are out of bounds bool withinBoundsA = TileMath.IsInsideBounds(nodeA.Point.Lon, nodeA.Point.Lat, bbox); bool withinBoundsB = TileMath.IsInsideBounds(nodeB.Point.Lon, nodeB.Point.Lat, bbox); if (withinBoundsA || withinBoundsB) { nodeA.Conn.Add(wayPointB); nodeB.Conn.Add(wayPointA); } } } } // Remove orphaned nodes outside of bounds result.Nodes = result.Nodes.Where(n => n.Conn.Count > 0).ToList(); _logger.LogInformation($"Parsed (nodes: {result.Nodes.Count}): {dbgMsg}"); return(result); }
private Task GenerateTiles(SpatialDataSet data, string outputFolderPath, int minZoom, int maxZoom, bool skipEmptyTiles, IProgress <int> progress) { var strColorName = TileBackgroundColor.SelectedValue.ToString().Replace("System.Windows.Media.Color ", ""); var backgroundColor = ((Color)ColorConverter.ConvertFromString(strColorName)).ToStyleColor(); var isQuadkeyFormat = (QuakeyTileFormat as RadioButton).IsChecked.Value; return(Task.Run(async() => { var bounds = data.BoundingBox.ToBMGeometry(); var renderEngine = new SpatialDataRenderEngine(_defaultStyle, false); var tileCount = 0; for (int z = minZoom; z <= maxZoom; z++) { var keys = TileMath.GetQuadKeysInView(bounds, z); foreach (var key in keys) { await renderEngine.RenderDataAsync(_dataSet, new ViewInfo(256, 256, key), backgroundColor); if (!skipEmptyTiles || !(await renderEngine.IsImageEmpty())) { string filePath; if (isQuadkeyFormat) { filePath = string.Format("{0}/{1}.{2}", outputFolderPath, key.Key, "png"); } else { //X Folder Path filePath = outputFolderPath + "/" + key.X; if (!Directory.Exists(filePath)) { Directory.CreateDirectory(filePath); } filePath += "/" + key.Y; if (!Directory.Exists(filePath)) { Directory.CreateDirectory(filePath); } filePath += "/" + key.ZoomLevel + ".png"; } using (var fs = File.Create(filePath)) { renderEngine.SaveImage(ImageFormat.PNG, fs); } } tileCount++; if (progress != null) { progress.Report(tileCount); } } } renderEngine.Dispose(); await Task.Delay(2000); })); }
public void RunWater2(int CellsPerRun) { // This will probably be single-thread only due to extensive modification of the heightmap. // Could interlock the f****r, but that'll be a performance nightmare with tight loops. var up = new Vector3(0f, 0f, 1f); var rand = new Random(); var tileDir = new Vector2(0); var turbulence = new Vector3(0); Func <int, float, float> LowestNeighbour = (i, h) => this.Map[i].WHeight < h ? this.Map[i].WHeight : h; foreach (var wp in this.WaterParticles) { //int celli = wp.Pos.CellIndex(this.Width, this.Height);// grab current cell index int cellx = wp.Pos.CellX(this.Width); int celly = wp.Pos.CellY(this.Height); int celli = C(cellx, celly); int cellnx, cellny; int cellox = cellx, celloy = celly; // check for oscillation in a small area bool needReset = false; wp.CarryingDecay *= this.WaterCarryingAmountDecayPerRun; //1.04 if (wp.CarryingDecay >= 1.0f) { this.Map[celli].Loose += wp.CarryingAmount; wp.Reset(rand.Next(this.Width), rand.Next(this.Height), rand);// reset particle } // run the particle for a number of cells for (int i = 0; i < CellsPerRun; i++) { // add some flowing water to the terrain so we can see it //this.Map[celli].MovingWater += 0.001f; // moved further down this.Map[celli].Carrying = this.Map[celli].Carrying * 0.5f + 0.5f * wp.CarryingAmount; // vis for carrying amount // get our current height float h = this.Map[celli].WHeight; // hole check - if the minimum height of our neighbours exceeds our own height, try to fill the hole float lowestNeighbour = this.Map[C(cellx - 1, celly)].WHeight; lowestNeighbour = LowestNeighbour(C(cellx + 1, celly), h); lowestNeighbour = LowestNeighbour(C(cellx, celly - 1), h); lowestNeighbour = LowestNeighbour(C(cellx, celly + 1), h); lowestNeighbour = LowestNeighbour(C(cellx - 1, celly - 1), h); lowestNeighbour = LowestNeighbour(C(cellx - 1, celly + 1), h); lowestNeighbour = LowestNeighbour(C(cellx + 1, celly - 1), h); lowestNeighbour = LowestNeighbour(C(cellx + 1, celly + 1), h); float ndiff = lowestNeighbour - h; if (ndiff > 0f) { if (wp.CarryingAmount > ndiff) { // carrying more than difference -> fill hole this.Map[celli].Loose += ndiff; wp.CarryingAmount -= ndiff; } else { // stuck in hole, reset needReset = true; break; } } // compute fall vector of current cell //var normal = CellNormal(cellx, celly); var fall = FallVector(cellx, celly); //FallVector(normal, up); // if fall vector points up, bail out if (fall.Z > 0.0f) { needReset = true; break; } turbulence.X = (float)rand.NextDouble() - 0.5f; turbulence.Y = (float)rand.NextDouble() - 0.5f; wp.Vel = wp.Vel * this.WaterMomentumFactor + fall + turbulence * this.WaterTurbulence; wp.Vel.Normalize(); // compute exit point and new cell coords tileDir.X = wp.Vel.X; tileDir.Y = wp.Vel.Y; // sanity check: If the direction is changing such that we're going to get stuck on an edge, move point out into tile if (tileDir.X < 0f) { if ((wp.Pos.X - (float)Math.Floor(wp.Pos.X)) < 0.05f) { wp.Pos.X += 0.2f; } } else { if ((wp.Pos.X - (float)Math.Floor(wp.Pos.X)) > 0.95f) { wp.Pos.X -= 0.2f; } } if (tileDir.Y < 0f) { if ((wp.Pos.Y - (float)Math.Floor(wp.Pos.Y)) < 0.05f) { wp.Pos.Y += 0.2f; } } else { if ((wp.Pos.Y - (float)Math.Floor(wp.Pos.Y)) > 0.95f) { wp.Pos.Y -= 0.2f; } } // compute exit var newPos = TileMath.TileIntersect(wp.Pos, tileDir, cellx, celly, out cellnx, out cellny); // if the intersection func has returned the same cell, we're stuck and need to reset if (cellx == cellnx && celly == cellny) { needReset = true; break; } // calculate index of next cell int cellni = C(cellnx, cellny); float nh = this.Map[cellni].WHeight; ndiff = nh - h; // check to see if we're being forced uphill. If we are we drop material to try and level with our new position. If we can't do that we reset. if (ndiff > 0f) { if (wp.CarryingAmount > ndiff) { float uphillDrop = ndiff; // carrying more than difference -> fill hole this.Map[celli].Loose += uphillDrop; wp.CarryingAmount -= uphillDrop; } else { // stuck in hole, reset needReset = true; break; } // collapse any material we've just deposited. //CollapseFrom(cellx, celly, this.WaterDepositWaterCollapseAmount); } else { float slope = (float)Math.Atan(-ndiff) / 1.570796f; // modify speed of particle wp.Speed = wp.Speed * this.WaterSpeedLowpassAmount + (1.0f - this.WaterSpeedLowpassAmount) * slope; } // calculate distance that we're travelling across cell. float crossdistance = Vector2.Distance(wp.Pos, newPos); // work out fraction of cell we're crossing, as a proportion of the length of the diagonal (root-2) crossdistance /= 1.4142136f; // add some moving water so we can see it. this.Map[celli].MovingWater += WaterAccumulatePerFrame * crossdistance; // calculate new carrying capacity wp.CarryingCapacity = (this.WaterCarryingCapacitySpeedCoefficient * wp.Speed) * (1.0f - wp.CarryingDecay); if (wp.CarryingCapacity > this.WaterMaxCarryingCapacity) { wp.CarryingCapacity = this.WaterMaxCarryingCapacity; } // if we're over our carrying capacity, start dropping material float cdiff = wp.CarryingAmount - wp.CarryingCapacity; if (cdiff > 0.0f) { cdiff *= this.WaterProportionToDropOnOverCapacity * crossdistance; // amount to drop // drop a portion of our material this.Map[cellni].Loose += cdiff; // drop at new location wp.CarryingAmount -= cdiff; //CollapseFrom(cellx, celly, this.WaterDepositWaterCollapseAmount); } else // we're under our carrying capacity, so do some erosion { cdiff = -cdiff; // multiply the remaining capacity to allow more material to be eroded. cdiff *= this.WaterErosionOverCapacityFactor; float loose = this.Map[celli].Loose; float hard = this.Map[celli].Hard; if (wp.Speed > this.WaterErosionMinSpeed) { float speed2 = wp.Speed - this.WaterErosionMinSpeed; //speed2 += 1f; speed2 = (float)Math.Sqrt(speed2 + 1f) - 1f; //speed2 -= 1f; float erosionFactor = ( ((this.WaterErosionSpeedCoefficientMin + speed2) * crossdistance * this.WaterErosionSpeedCoefficient) * // more speed = more erosion. (1f + this.Map[celli].MovingWater * this.WaterErosionWaterDepthMultiplier) // erode more where there is lots of water. ) * (1.0f - wp.CarryingDecay); // decay erosion factor with particle age so they die gracefully. Decay faster than carrying capacity. // we can only erode the difference between our height and our lowest neighbour. //erosionFactor = erosionFactor.ClampInclusive(0f, (h - lowestNeighbour) * 3.0f); //if (erosionFactor > (h - lowestNeighbour)) //{ // erosionFactor = h - lowestNeighbour; //} float looseErodeAmount = erosionFactor; // erosion coefficient for loose material //float hardErodeAmount = erosionFactor; // erosion coefficient for hard material if (looseErodeAmount > cdiff) { looseErodeAmount = cdiff; } //this.Map[celli].Water = this.Map[celli].Water * 0.5f + 0.5f * erosionFactor; // vis for erosion factor // first of all, see if we can pick up any loose material. if (loose > 0.0f) { if (looseErodeAmount > loose) { looseErodeAmount = loose; } this.Map[celli].Loose -= looseErodeAmount; wp.CarryingAmount += looseErodeAmount; this.Map[celli].Erosion += looseErodeAmount; cdiff -= looseErodeAmount; //CollapseTo(cellx, celly, this.WaterErosionCollapseToAmount); } // if we've got any erosion potential left, use it float hardErodeAmount = (erosionFactor - looseErodeAmount) * this.WaterErosionHardErosionFactor; if (hardErodeAmount > cdiff) { hardErodeAmount = cdiff; } if (hardErodeAmount > 0.0f) { this.Map[celli].Hard -= hardErodeAmount; wp.CarryingAmount += hardErodeAmount; // loose material is less dense than hard, so make it greater. this.Map[celli].Erosion += hardErodeAmount; //CollapseTo(cellx, celly, this.WaterErosionCollapseToAmount); } } } //} // move particle params wp.Pos = newPos; cellx = cellnx; // this may not work across loop runs. May need to store on particle. celly = cellny; celli = cellni; } // if we haven't moved further than a portion of the cells per run (manhattan distance), then decay carrying capacity faster if (Math.Abs(cellx - cellox) + Math.Abs(celly - celloy) < CellsPerRun / 2) { wp.CarryingDecay *= 1.2f; if (wp.CarryingDecay > 1.0f) { needReset = true; } } if (needReset) { this.Map[celli].Loose += wp.CarryingAmount.ClampInclusive(0f, 1000f); wp.Reset(rand.Next(this.Width), rand.Next(this.Height), rand);// reset particle } } }
async Task LoadTileAtPoint(GeoCoord point) { long tileId = TileMath.GetTileId(point.Lon, point.Lat, Zoom); await LoadTile(tileId); }