public override void Draw(BoundingBox boundingBox, sbyte zoomLevel, ICanvas canvas, Point topLeftPoint) { lock (this) { if (this.latLong == null || this.bitmap == null) { return; } long mapSize = MercatorProjection.GetMapSize(zoomLevel, this.displayModel.TileSize); double pixelX = MercatorProjection.LongitudeToPixelX(this.latLong.Longitude, mapSize); double pixelY = MercatorProjection.LatitudeToPixelY(this.latLong.Latitude, mapSize); int halfBitmapWidth = this.bitmap.Width / 2; int halfBitmapHeight = this.bitmap.Height / 2; int left = (int)(pixelX - topLeftPoint.X - halfBitmapWidth + this.horizontalOffset); int top = (int)(pixelY - topLeftPoint.Y - halfBitmapHeight + this.verticalOffset); int right = left + this.bitmap.Width; int bottom = top + this.bitmap.Height; Rectangle bitmapRectangle = new Rectangle(left, top, right, bottom); Rectangle canvasRectangle = new Rectangle(0, 0, canvas.Width, canvas.Height); if (!canvasRectangle.Intersects(bitmapRectangle)) { return; } canvas.DrawBitmap(this.bitmap, left, top); } }
public virtual bool Contains(Point center, Point point) { lock (this) { Rectangle r = new Rectangle(center.X - (float)bitmap.Width / 2 + this.horizontalOffset, center.Y - (float)bitmap.Height / 2 + this.verticalOffset, center.X + (float)bitmap.Width / 2 + this.horizontalOffset, center.Y + (float)bitmap.Height / 2 + this.verticalOffset); return(r.Contains(point)); } }
/// <summary> /// Fills the area outside the specificed rectangle with color. /// This method is used to blank out areas that fall outside the map area. </summary> /// <param name="color"> the fill color for the outside area </param> /// <param name="insideArea"> the inside area on which not to draw </param> internal virtual void FillOutsideAreas(int color, Rectangle insideArea) { if (canvas == null) { return; } this.canvas.SetClipDifference((int)insideArea.Left, (int)insideArea.Top, (int)insideArea.Width, (int)insideArea.Height); this.canvas.FillColor(color); this.canvas.ResetClip(); }
public override void Draw(BoundingBox boundingBox, sbyte zoomLevel, ICanvas canvas, Point topLeftPoint) { lock (this) { if (this.latLong == null || (this.paintStroke == null && this.paintFill == null)) { return; } double latitude = this.latLong.Latitude; double longitude = this.latLong.Longitude; long mapSize = MercatorProjection.GetMapSize(zoomLevel, displayModel.TileSize); int pixelX = (int)(MercatorProjection.LongitudeToPixelX(longitude, mapSize) - topLeftPoint.X); int pixelY = (int)(MercatorProjection.LatitudeToPixelY(latitude, mapSize) - topLeftPoint.Y); int radiusInPixel = GetRadiusInPixels(latitude, zoomLevel); Rectangle canvasRectangle = new Rectangle(0, 0, canvas.Width, canvas.Height); if (!canvasRectangle.IntersectsCircle(pixelX, pixelY, radiusInPixel)) { return; } if (this.paintStroke != null) { if (this.keepAligned) { this.paintStroke.SetBitmapShaderShift = topLeftPoint; } canvas.DrawCircle(pixelX, pixelY, radiusInPixel, this.paintStroke); } if (this.paintFill != null) { if (this.keepAligned) { this.paintFill.SetBitmapShaderShift = topLeftPoint; } canvas.DrawCircle(pixelX, pixelY, radiusInPixel, this.paintFill); } } }
/// <summary> /// Called when a job needs to be executed. /// </summary> /// <param name="rendererJob"> /// the job that should be executed. </param> public virtual ITileBitmap ExecuteJob(RendererJob rendererJob) { RenderTheme renderTheme; try { // Wait until RenderTheme is ready renderTheme = rendererJob.renderThemeFuture.Result; } catch (Exception e) { LOGGER.Fatal("Error to retrieve render theme from future", e); return(null); } RenderContext renderContext = null; try { renderContext = new RenderContext(renderTheme, rendererJob, new CanvasRasterer(graphicFactory)); if (RenderBitmap(renderContext)) { ITileBitmap bitmap = null; if (this.mapDatabase != null) { MapReadResult mapReadResult = this.mapDatabase.ReadMapData(rendererJob.tile); ProcessReadMapData(renderContext, mapReadResult); } if (!rendererJob.labelsOnly) { bitmap = this.graphicFactory.CreateTileBitmap(renderContext.rendererJob.tile.TileSize, renderContext.rendererJob.hasAlpha); bitmap.Timestamp = rendererJob.mapDataStore.GetDataTimestamp(renderContext.rendererJob.tile); renderContext.canvasRasterer.CanvasBitmap = bitmap; if (!rendererJob.hasAlpha && rendererJob.displayModel.BackgroundColor != renderContext.renderTheme.MapBackground) { renderContext.canvasRasterer.Fill(renderContext.renderTheme.MapBackground); } renderContext.canvasRasterer.DrawWays(renderContext); } if (renderLabels) { ISet <MapElementContainer> labelsToDraw = ProcessLabels(renderContext); // now draw the ways and the labels renderContext.canvasRasterer.DrawMapElements(labelsToDraw, renderContext.rendererJob.tile); } else { // store elements for this tile in the label cache this.labelStore.StoreMapItems(renderContext.rendererJob.tile, renderContext.labels); } if (!rendererJob.labelsOnly && renderContext.renderTheme.HasMapBackgroundOutside()) { // blank out all areas outside of map Rectangle insideArea = this.mapDatabase.BoundingBox.GetPositionRelativeToTile(renderContext.rendererJob.tile); if (!rendererJob.hasAlpha) { renderContext.canvasRasterer.FillOutsideAreas(renderContext.renderTheme.MapBackgroundOutside, insideArea); } else { renderContext.canvasRasterer.FillOutsideAreas(Color.TRANSPARENT, insideArea); } } return(bitmap); } // outside of map area with background defined: return(CreateBackgroundBitmap(renderContext)); } finally { if (renderContext != null) { renderContext.Destroy(); } } }
/// <summary> /// Finds the segments of a line along which a name can be drawn and then adds WayTextContainers /// to the list of drawable items. /// </summary> /// <param name="tile"> the tile on which the text will be drawn. </param> /// <param name="text"> the text to draw </param> /// <param name="priority"> priority of the text </param> /// <param name="dy"> if 0, then a line parallel to the coordinates will be calculated first </param> /// <param name="fill"> fill paint for text </param> /// <param name="stroke"> stroke paint for text </param> /// <param name="coordinates"> the list of way coordinates </param> /// <param name="currentLabels"> the list of labels to which a new WayTextContainer will be added </param> internal static void RenderText(Tile tile, string text, Display display, int priority, float dy, IPaint fill, IPaint stroke, Point[][] coordinates, ICollection <MapElementContainer> currentLabels) { // Calculate the way name length plus some margin of safety int wayNameWidth = (stroke == null) ? fill.GetTextWidth(text) + WAYNAME_SAFETY_MARGIN * 2 : stroke.GetTextWidth(text) + WAYNAME_SAFETY_MARGIN * 2; // Compute the tile boundary on which we render the name. // We make the tile smaller because otherwise we sometimes write the text beyond the tile boundary // (e.g. a road that runs parallel just below a tile boundary) double textHeight = (stroke == null) ? fill.GetTextHeight(text) : stroke.GetTextHeight(text); Rectangle tileBoundary = tile.BoundaryAbsolute.Envelope(-textHeight); int skipPixels = 0; Point[] c; if (dy == 0f) { c = coordinates[0]; } else { c = RendererUtils.ParallelPath(coordinates[0], dy); } // iterate through the segments to find those long enough to draw the way name on them for (int i = 1; i < c.Length; ++i) { LineSegment currentSegment = new LineSegment(c[i - 1], c[i]); double currentLength = currentSegment.Length(); skipPixels -= (int)currentLength; if (skipPixels > 0) { // we should still be skipping pixels, so skip this segment. Note that // this does not guarantee that we skip any certain minimum of pixels, // it is more a rule of thumb. continue; } if (currentLength < wayNameWidth) { // no point trying to clip, the segment is too short anyway continue; } // clip the current segment to the tile, so that we never overlap tile boundaries // with the way name LineSegment drawableSegment = currentSegment.ClipToRectangle(tileBoundary); if (drawableSegment == null) { // this happens if the segment does not intersect the tile continue; } double segmentLengthInPixel = drawableSegment.Length(); if (segmentLengthInPixel < wayNameWidth) { // not enough space to draw name on this segment continue; } // now calculate the actually used part of the segment to ensure the bbox of the waytext container // is as small as possible. The offset at the beginning/end is to ensure that we are a bit off the center // of an intersection (otherwise we have more collisions at the intersection) LineSegment actuallyUsedSegment = drawableSegment.SubSegment(WAYNAME_SAFETY_MARGIN, wayNameWidth - WAYNAME_SAFETY_MARGIN); // check to prevent inverted way names if (actuallyUsedSegment.Start.X <= actuallyUsedSegment.End.X) { currentLabels.Add(new WayTextContainer(actuallyUsedSegment.Start, actuallyUsedSegment.End, display, priority, text, fill, stroke, textHeight)); } else { currentLabels.Add(new WayTextContainer(actuallyUsedSegment.End, actuallyUsedSegment.Start, display, priority, text, fill, stroke, textHeight)); } skipPixels = wayNameWidth; } }