Пример #1
0
        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);
        }
Пример #2
0
            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);
            }
Пример #3
0
        public PointF LatLongToPointF(Coordinate coord)
        {
            var globalPx = TileMath.LocationToGlobalPixel(coord, _zoom);

            globalPx.X -= _topLeftGlobalPixel.X;
            globalPx.Y -= _topLeftGlobalPixel.Y;
            return(globalPx);
        }
Пример #4
0
        public ViewInfo(int width, int height, Coordinate topLeftCorner, double zoom)
        {
            _width  = width;
            _height = height;
            _zoom   = zoom;

            _topLeftGlobalPixel = TileMath.LocationToGlobalPixel(topLeftCorner, zoom);
        }
Пример #5
0
        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.
            }
        }
Пример #6
0
        /// <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);
        }
Пример #8
0
        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);
            }));
        }
Пример #9
0
        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
                }
            }
        }
Пример #10
0
        async Task LoadTileAtPoint(GeoCoord point)
        {
            long tileId = TileMath.GetTileId(point.Lon, point.Lat, Zoom);

            await LoadTile(tileId);
        }