diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index b1552f3..fb6ed96 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -16,7 +16,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v2
with:
- dotnet-version: 6.0.x
+ dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore MapToolkit.sln
- name: Build
diff --git a/DemUtility/DemUtility.csproj b/DemUtility/DemUtility.csproj
index d9f4449..9bcc381 100644
--- a/DemUtility/DemUtility.csproj
+++ b/DemUtility/DemUtility.csproj
@@ -2,7 +2,7 @@
Exe
- net6.0
+ net8.0
enable
dem
diff --git a/MapToolkit.Drawing.Test/MapToolkit.Drawing.Test.csproj b/MapToolkit.Drawing.Test/MapToolkit.Drawing.Test.csproj
index b20bdef..2021a3d 100644
--- a/MapToolkit.Drawing.Test/MapToolkit.Drawing.Test.csproj
+++ b/MapToolkit.Drawing.Test/MapToolkit.Drawing.Test.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net8.0
enable
enable
diff --git a/MapToolkit.Drawing.Topographic/ColorPalette.cs b/MapToolkit.Drawing.Topographic/ColorPalette.cs
new file mode 100644
index 0000000..9b1541d
--- /dev/null
+++ b/MapToolkit.Drawing.Topographic/ColorPalette.cs
@@ -0,0 +1,29 @@
+using SixLabors.ImageSharp;
+
+namespace MapToolkit.Drawing.Topographic
+{
+ internal class ColorPalette
+ {
+ internal static readonly ColorPalette Default = new ColorPalette();
+
+ public Color ContourMinor { get; set; } = Color.ParseHex("D4C5BF");
+ public Color ContourMajor { get; set; } = Color.ParseHex("B29A94");
+ public Color ForestFill { get; set; } = Color.ParseHex("D1FEB9");
+ public Color ForestBorder { get; set; } = Color.ParseHex("8AE854");
+ public Color WaterFill { get; set; } = Color.ParseHex("B3D9FE");
+ public Color WaterBorder { get; set; } = Color.ParseHex("77A5E1");
+ public Color BuildingFill { get; set; } = Color.ParseHex("808080");
+ public Color BuildingBorder { get; set; } = Color.ParseHex("000000");
+ public Color RocksSymbol { get; set; } = Color.ParseHex("C0C0C0");
+ public Color RocksFill { get; set; } = Color.ParseHex("C0C0C080");
+ public Color RocksBorder { get; set; } = Color.ParseHex("808080");
+ public Color MainRoad { get; set; } = Color.ParseHex("FE002C");
+ public Color SecondaryRoad { get; set; } = Color.ParseHex("FF9643");
+ public Color Road { get; set; } = Color.White;
+ public Color RoadBorder { get; set; } = Color.Black;
+ public Color Trail { get; set; } = Color.ParseHex("C0C0C0");
+ public Color Graticule { get; set; } = Color.ParseHex("0071D6");
+ public Color TextBorder { get; set; } = Color.ParseHex("FFFFFFCC");
+
+ }
+}
diff --git a/MapToolkit.Drawing.Topographic/ITopoMapData.cs b/MapToolkit.Drawing.Topographic/ITopoMapData.cs
new file mode 100644
index 0000000..e9600b9
--- /dev/null
+++ b/MapToolkit.Drawing.Topographic/ITopoMapData.cs
@@ -0,0 +1,32 @@
+using GeoJSON.Text.Geometry;
+using MapToolkit.DataCells;
+
+namespace MapToolkit.Drawing.Topographic
+{
+ public interface ITopoMapData
+ {
+ string Title { get; }
+
+ Dictionary? Roads { get; }
+
+ Dictionary? Bridges { get; }
+
+ MultiPolygon? ForestPolygons { get; }
+
+ MultiPolygon? RockPolygons { get; }
+
+ MultiPolygon? BuildingPolygons { get; }
+
+ MultiPolygon? WaterPolygons { get; }
+
+ IDemDataView DemDataCell { get; }
+
+ List? Names { get; }
+
+ List? Icons { get; }
+
+ MultiLineString? Powerlines { get; }
+
+ MultiPolygon? FortPolygons { get; }
+ }
+}
diff --git a/MapToolkit.Drawing.Topographic/ITopoMapPdfRenderOptions.cs b/MapToolkit.Drawing.Topographic/ITopoMapPdfRenderOptions.cs
new file mode 100644
index 0000000..4b9153b
--- /dev/null
+++ b/MapToolkit.Drawing.Topographic/ITopoMapPdfRenderOptions.cs
@@ -0,0 +1,11 @@
+namespace MapToolkit.Drawing.Topographic
+{
+ public interface ITopoMapPdfRenderOptions
+ {
+ public string FileName { get; }
+
+ public string TargetDirectory { get; }
+
+ public string Attribution { get; }
+ }
+}
diff --git a/MapToolkit.Drawing.Topographic/IconsRender.cs b/MapToolkit.Drawing.Topographic/IconsRender.cs
new file mode 100644
index 0000000..f8cdb5e
--- /dev/null
+++ b/MapToolkit.Drawing.Topographic/IconsRender.cs
@@ -0,0 +1,98 @@
+using MapToolkit.Drawing;
+using SixLabors.ImageSharp;
+
+namespace MapToolkit.Drawing.Topographic
+{
+ internal class IconsRender
+ {
+ internal static IDrawIcon Dot(IDrawSurface w)
+ {
+ var style = w.AllocateBrushStyle(Color.Black);
+ return w.AllocateIcon(new Vector(5, 5), (target) =>
+ {
+ target.DrawCircle(new Vector(2.5, 2.5), 2, style);
+ });
+ }
+
+ internal static IDrawIcon Hospital(IDrawSurface w)
+ {
+ var border = w.AllocateStyle("FFFFFF", "FF0033");
+ var cross = w.AllocatePenStyle("FF0033", 2);
+ return w.AllocateIcon(new Vector(12, 12), (target) =>
+ {
+ target.DrawRectangle(new Vector(0, 0), new Vector(11, 11), border);
+ target.DrawPolyline(new[] { new Vector(0, 5.5), new Vector(11, 5.5) }, cross);
+ target.DrawPolyline(new[] { new Vector(5.5, 0), new Vector(5.5, 11) }, cross);
+ });
+ }
+
+ internal static IDrawIcon WaterTower(IDrawSurface w)
+ {
+ var style = w.AllocateBrushStyle("0080FF");
+ return w.AllocateIcon(new Vector(13, 13), (target) => target.DrawCircle(new Vector(6, 6), 6, style));
+ }
+
+ internal static IDrawIcon TechnicalTower(IDrawSurface w)
+ {
+ var style = w.AllocateStyle(Color.White, Color.Black, 1);
+ var stylea = w.AllocatePenStyle(Color.Black, 1);
+ return w.AllocateIcon(new Vector(13, 13), (target) =>
+ {
+ target.DrawCircle(new Vector(6, 6), 6, style);
+ target.DrawPolyline(new[] { new Vector(1.76, 1.76), new Vector(10.24, 10.24) }, stylea);
+ target.DrawPolyline(new[] { new Vector(10.24, 1.76), new Vector(1.76, 10.24) }, stylea);
+ });
+ }
+ internal static IDrawIcon Transmitter(IDrawSurface w)
+ {
+ var full = w.AllocateBrushStyle(Color.White);
+ var center = w.AllocateStyle(Color.Black, Color.Black, 1);
+ var line = w.AllocatePenStyle(Color.Black, 2);
+
+ return w.AllocateIcon(new Vector(13, 13), (target) =>
+ {
+ var c = new Vector(6, 6);
+ target.DrawCircle(c, 6, full);
+ target.DrawCircle(c, 3, center);
+ target.DrawArc(c, 6, 50, 70, line);
+ target.DrawArc(c, 6, 140, 70, line);
+ target.DrawArc(c, 6, 230, 70, line);
+ target.DrawArc(c, 6, 320, 70, line);
+ });
+ }
+
+ internal static IDrawIcon WindTurbine(IDrawSurface w)
+ {
+ var style = w.AllocatePenStyle(Color.Black, 2);
+ var stylea = w.AllocatePenStyle(Color.Black, 1);
+ var stylec = w.AllocateBrushStyle(Color.Black);
+ return w.AllocateIcon(new Vector(32, 32), (target) => WindTurbine(target, style, stylea, stylec));
+ }
+
+ internal static void WindTurbine(IDrawSurface w, IDrawStyle style, IDrawStyle stylea, IDrawStyle stylec)
+ {
+ var center = new Vector(16, 16);
+ w.DrawPolyline(
+ new[]
+ {
+ center + new Vector( 0.9961946 * 2.5, -0.0871557 * 2.5),
+ center + new Vector( 0.6427876 * 15, 0.7660444 * 15),
+ center + new Vector( 0.5000000 * 15, 0.8660254 * 15),
+ center + new Vector(-0.4226182 * 2.5, 0.9063077 * 2.5),
+ center + new Vector(-0.9848077 * 15, 0.1736481 * 15),
+ center + new Vector(-1.0 * 15, 0 * 15),
+ center + new Vector(-0.5735764 * 2.5, -0.8191520 * 2.5),
+ center + new Vector( 0.3420201 * 15, -0.9396926 * 15),
+ center + new Vector( 0.5000000 * 15, -0.8660254 * 15),
+ center + new Vector( 0.9961946 * 2.5, -0.0871557 * 2.5)
+ }, style);
+ w.DrawArc(center, 10, 75, 80, stylea);
+ w.DrawArc(center, 15, 70, 90, stylea);
+ w.DrawArc(center, 10, 195, 80, stylea);
+ w.DrawArc(center, 15, 190, 90, stylea);
+ w.DrawArc(center, 10, -45, 80, stylea);
+ w.DrawArc(center, 15, -50, 90, stylea);
+ w.DrawCircle(center, 1, stylec);
+ }
+ }
+}
diff --git a/MapToolkit.Drawing.Topographic/LegendRender.cs b/MapToolkit.Drawing.Topographic/LegendRender.cs
new file mode 100644
index 0000000..3bf28e0
--- /dev/null
+++ b/MapToolkit.Drawing.Topographic/LegendRender.cs
@@ -0,0 +1,239 @@
+using MapToolkit.Drawing;
+using MapToolkit.Drawing.PdfRender;
+using MapToolkit.Projections;
+using SixLabors.ImageSharp;
+
+namespace MapToolkit.Drawing.Topographic
+{
+ internal class LegendRender
+ {
+ public const double LegendWidthPoints = LegendWidth * PaperSize.OnePixelAt300Dpi;
+ public const double LegendHeightPoints = LegendHeight * PaperSize.OnePixelAt300Dpi;
+
+ // Pixels
+
+ public const double LegendWidth = 1300;
+ public const double LegendHeight = 2000;
+ private const double InsideMargin = 40;
+
+ private const double LegendHalfWidth = LegendWidth / 2;
+
+ private const double LegendContentStart = InsideMargin;
+ private const double LegendContentCenter = LegendHalfWidth;
+ private const double LegendContentEnd = LegendWidth - InsideMargin;
+
+ private const double LegendContentP1 = LegendContentCenter;
+ private const double LegendContentW = (LegendContentEnd - LegendContentCenter - InsideMargin * 2) / 3;
+ private const double LegendContentP2 = LegendContentP1 + LegendContentW;
+ private const double LegendContentP3 = LegendContentP2 + InsideMargin;
+ private const double LegendContentP4 = LegendContentP3 + LegendContentW;
+ private const double LegendContentP5 = LegendContentP4 + InsideMargin;
+ private const double LegendContentP6 = LegendContentEnd;
+
+ public static void RenderLegend(IDrawSurface w, ITopoMapData data, ITopoMapPdfRenderOptions opts, int scale)
+ {
+ RenderLegend(w, data.Title, opts.Attribution, scale);
+ }
+
+ public static void RenderLegend(IDrawSurface w, string title, string credits, int scale, string? fullCopyrightNotice = null, string? licenseNotice = null)
+ {
+ var style = TopoMapStyle.CreateFull(w, ColorPalette.Default, true);
+
+ var titleText = w.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Regular, 160, new SolidColorBrush(Color.Black), null, false, TextAnchor.TopCenter);
+ var subTitleText = w.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Regular, 60, new SolidColorBrush(Color.Black), null, false, TextAnchor.TopCenter);
+ var normalText = w.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Regular, 32, new SolidColorBrush(Color.Black), null, false, TextAnchor.TopCenter);
+ var smallText = w.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Regular, 25, new SolidColorBrush(Color.Black), null, false, TextAnchor.TopCenter);
+ var normalTextLeft = w.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Regular, 32, new SolidColorBrush(Color.Black), null, false, TextAnchor.CenterLeft);
+
+ w.DrawRectangle(new Vector(0, 0), new Vector(LegendWidth, LegendHeight), w.AllocateStyle(Color.White, Color.Black, 2));
+
+ w.DrawText(new Vector(LegendContentCenter, 20), "MapToolkit Topographic Map", subTitleText);
+ w.DrawText(new Vector(LegendContentCenter, 90), title, titleText);
+ if (scale == -1)
+ {
+ w.DrawText(new Vector(LegendContentCenter, 300), $"Dynamic scale : see bottom left of map", normalText);
+ }
+ else
+ {
+ w.DrawText(new Vector(LegendContentCenter, 300), $"Scale 1 : {scale} 000 - 1 cm = {scale * 10} m", normalText);
+ }
+ w.DrawText(new Vector(LegendContentCenter, 345), $"Version {DateTime.Now:yyyy-MM-dd}", smallText);
+
+ RoadLegend(w, style, normalTextLeft, 500, TopoMapPathType.Main, "Main road");
+ RoadLegend(w, style, normalTextLeft, 560, TopoMapPathType.Secondary, "Secondary road");
+ RoadLegend(w, style, normalTextLeft, 620, TopoMapPathType.Road, "City road, Other road");
+ RoadLegend(w, style, normalTextLeft, 680, TopoMapPathType.Track, "Vehicle track");
+ RoadLegend(w, style, normalTextLeft, 740, TopoMapPathType.Trail, "Foot trail");
+
+ w.DrawText(new Vector(LegendContentStart, 800), $"Bridges Main, Secondary, Road", normalTextLeft);
+ RenderBridge(w, style, TopoMapPathType.Main, new Vector(LegendContentP1, 800), new Vector(LegendContentP2, 800));
+ RenderBridge(w, style, TopoMapPathType.Secondary, new Vector(LegendContentP3, 800), new Vector(LegendContentP4, 800));
+ RenderBridge(w, style, TopoMapPathType.Road, new Vector(LegendContentP5, 800), new Vector(LegendContentP6, 800));
+
+ w.DrawText(new Vector(LegendContentStart, 860), $"Forest, Rocks, Water", normalTextLeft);
+ w.DrawRectangle(new Vector(LegendContentP1, 840), new Vector(LegendContentP2, 880), style.forest);
+ w.DrawRectangle(new Vector(LegendContentP3, 840), new Vector(LegendContentP4, 880), style.rocks);
+ w.DrawRectangle(new Vector(LegendContentP5, 840), new Vector(LegendContentP6, 880), style.water);
+
+ if (style.WindPowerPlant != null && style.WaterTower != null && style.Transmitter != null)
+ {
+ w.DrawText(new Vector(LegendContentStart, 920), $"Wind turbine, Water tower, Transmitter", normalTextLeft);
+ w.DrawIcon(new Vector((LegendContentP1 + LegendContentP2) / 2, 920), style.WindPowerPlant);
+ w.DrawIcon(new Vector((LegendContentP3 + LegendContentP4) / 2, 920), style.WaterTower);
+ w.DrawIcon(new Vector((LegendContentP5 + LegendContentP6) / 2, 920), style.Transmitter);
+ }
+
+ w.DrawText(new Vector(LegendContentCenter, 1810), $"Original Map {credits}", smallText);
+
+ if (!string.IsNullOrEmpty(fullCopyrightNotice))
+ {
+ w.DrawText(new Vector(LegendContentCenter, 1850), fullCopyrightNotice, smallText);
+ }
+ if (scale == -1)
+ {
+ w.DrawText(new Vector(LegendContentCenter, 1890), "Web Map created by GrueArbre", smallText);
+ }
+ else
+ {
+ w.DrawText(new Vector(LegendContentCenter, 1890), "Print Map created by GrueArbre", smallText);
+ }
+ if (!string.IsNullOrEmpty(fullCopyrightNotice))
+ {
+ w.DrawText(new Vector(LegendContentCenter, 1930), "ONLY FOR ARMA USE - NO COMMERCIAL USE - Licensed under APL-SA", smallText);
+ }
+ }
+
+ private static void RenderBridge(IDrawSurface w, TopoMapStyle style, TopoMapPathType type, Vector begin, Vector end)
+ {
+ var bg = style.roadBackground[(int)type];
+ var fg = style.roadForeground[(int)type];
+ var bbg = style.bridgeBackground[(int)type];
+ var bfg = style.bridgeForeground[(int)type];
+ if (bg != null)
+ {
+ w.DrawPolyline(new[] { begin, end }, bg);
+ }
+ if (fg != null)
+ {
+ w.DrawPolyline(new[] { begin, end }, fg);
+ }
+
+ var bbegin = (begin * 2 + end) / 3;
+ var bend = (begin + end * 2) / 3;
+
+ if (bbg != null)
+ {
+ w.DrawPolyline(new[] { bbegin, bend }, bbg);
+
+ TopoMapRender.BridgeLimit(w, bbegin, bend, style.BridgeLimit);
+ TopoMapRender.BridgeLimit(w, bend, bbegin, style.BridgeLimit);
+ }
+ if (bfg != null)
+ {
+ w.DrawPolyline(new[] { bbegin, bend }, bfg);
+ }
+ }
+
+ private static void RoadLegend(IDrawSurface w, TopoMapStyle style, IDrawTextStyle textStyle, double top, TopoMapPathType type, string label)
+ {
+ w.DrawText(new Vector(LegendContentStart, top), label, textStyle);
+
+ var bg = style.roadBackground[(int)type];
+ var fg = style.roadForeground[(int)type];
+ if (bg != null)
+ {
+ w.DrawPolyline(new[] { new Vector(LegendContentP1, top), new Vector(LegendContentP6, top) }, bg);
+ }
+ if (fg != null)
+ {
+ w.DrawPolyline(new[] { new Vector(LegendContentP1, top), new Vector(LegendContentP6, top) }, fg);
+ }
+ }
+
+ public static void DrawMiniMap(IDrawSurface w, ITopoMapData data, List? tiles, TopoMapPdfTile? current, double size = LegendWidth)
+ {
+ var projection = new NoProjectionArea(data.DemDataCell.Start, data.DemDataCell.End, new Vector(size, size));
+
+ var tileNameText = w.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Regular, 12 / 0.24, new SolidColorBrush(Color.Black), null, false, TextAnchor.BottomCenter);
+ var cityNameText = w.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Regular, 8 / 0.24, new SolidColorBrush(Color.Black), null, false, TextAnchor.CenterLeft);
+
+ var cityCircle = w.AllocatePenStyle(Color.Black, 1);
+ var tileLimit = w.AllocatePenStyle(Color.Gray, 2);
+ var currentTile = w.AllocateStyle(new SolidColorBrush(Color.ParseHex("FFFF0080")), new Pen(Color.Gray, 4));
+
+ if (data.ForestPolygons != null)
+ {
+ TopoMapRender.DrawPolygonsSimplified(w, projection, data.ForestPolygons, w.AllocateBrushStyle("D1FEB9"));
+ }
+ if (data.RockPolygons != null)
+ {
+ TopoMapRender.DrawPolygonsSimplified(w, projection, data.RockPolygons, w.AllocateBrushStyle("C0C0C080"));
+ }
+ if (data.WaterPolygons != null)
+ {
+ TopoMapRender.DrawPolygonsSimplified(w, projection, data.WaterPolygons, w.AllocateBrushStyle("B3D9FE"));
+ }
+
+ var mains = data.Roads?[TopoMapPathType.Main]?.Coordinates;
+ if (mains != null)
+ {
+ var mr = w.AllocatePenStyle("FE002C", 2);
+ foreach (var road in mains)
+ {
+ var simpler = LevelOfDetailHelper.SimplifyAnglesAndDistances(road.Coordinates.Select(projection.Project).ToList(), 1);
+ if (simpler.Count > 0)
+ {
+ w.DrawPolyline(simpler, mr);
+ }
+ }
+ }
+ if (tiles != null)
+ {
+ foreach (var tile in tiles)
+ {
+ if (tile != current)
+ {
+ w.DrawPolyline(GetTile(projection, tile), tileLimit);
+ }
+ }
+ }
+ if (current != null)
+ {
+ w.DrawPolygon(GetTile(projection, current), currentTile);
+ }
+
+ if (data.Names != null)
+ {
+ foreach (var city in data.Names)
+ {
+ if (city.Type == TopoLocationType.City || city.Type == TopoLocationType.Local || city.Type == TopoLocationType.Village)
+ {
+ var pc = projection.Project(city.Position);
+ w.DrawCircle(pc, 1.5f, cityCircle);
+ w.DrawText(pc + new Vector(15, 0), city.Name, cityNameText);
+ }
+ }
+ }
+
+ if (tiles != null)
+ {
+ foreach (var tile in tiles)
+ {
+ w.DrawText((projection.Project(tile.Min) + projection.Project(tile.Max)) / 2, tile.Name, tileNameText);
+ }
+ }
+
+ w.DrawRectangle(new Vector(0, 0), new Vector(size, size), w.AllocatePenStyle(Color.Black, 2));
+ }
+
+ private static Vector[] GetTile(NoProjectionArea projection, TopoMapPdfTile tile)
+ {
+ return new[]{
+ projection.Project(tile.Min),
+ projection.Project(new Coordinates(tile.Min.Latitude, tile.Max.Longitude)),
+ projection.Project(tile.Max),
+ projection.Project(new Coordinates(tile.Max.Latitude, tile.Min.Longitude)),
+ projection.Project(tile.Min)};
+ }
+ }
+}
diff --git a/MapToolkit.Drawing.Topographic/MapToolkit.Drawing.Topographic.csproj b/MapToolkit.Drawing.Topographic/MapToolkit.Drawing.Topographic.csproj
new file mode 100644
index 0000000..10085b4
--- /dev/null
+++ b/MapToolkit.Drawing.Topographic/MapToolkit.Drawing.Topographic.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/MapToolkit.Drawing.Topographic/TopoIcon.cs b/MapToolkit.Drawing.Topographic/TopoIcon.cs
new file mode 100644
index 0000000..9552d0c
--- /dev/null
+++ b/MapToolkit.Drawing.Topographic/TopoIcon.cs
@@ -0,0 +1,15 @@
+namespace MapToolkit.Drawing.Topographic
+{
+ public sealed class TopoIcon
+ {
+ public TopoIcon(TopoIconType mapType, Coordinates coordinates)
+ {
+ MapType = mapType;
+ Coordinates = coordinates;
+ }
+
+ public TopoIconType MapType { get; }
+
+ public Coordinates Coordinates { get; }
+ }
+}
diff --git a/MapToolkit.Drawing.Topographic/TopoIconType.cs b/MapToolkit.Drawing.Topographic/TopoIconType.cs
new file mode 100644
index 0000000..c24b168
--- /dev/null
+++ b/MapToolkit.Drawing.Topographic/TopoIconType.cs
@@ -0,0 +1,11 @@
+namespace MapToolkit.Drawing.Topographic
+{
+ public enum TopoIconType
+ {
+ WindPowerPlant,
+ WaterTower,
+ Transmitter,
+ ElectricityPylon,
+ Hospital
+ }
+}
diff --git a/MapToolkit.Drawing.Topographic/TopoLocation.cs b/MapToolkit.Drawing.Topographic/TopoLocation.cs
new file mode 100644
index 0000000..2abda38
--- /dev/null
+++ b/MapToolkit.Drawing.Topographic/TopoLocation.cs
@@ -0,0 +1,18 @@
+namespace MapToolkit.Drawing.Topographic
+{
+ public sealed class TopoLocation
+ {
+ public TopoLocation(string name, TopoLocationType type, Coordinates position)
+ {
+ Name = name;
+ Type = type;
+ Position = position;
+ }
+
+ public string Name { get; }
+
+ public TopoLocationType Type { get; }
+
+ public Coordinates Position { get; }
+ }
+}
\ No newline at end of file
diff --git a/MapToolkit.Drawing.Topographic/TopoLocationType.cs b/MapToolkit.Drawing.Topographic/TopoLocationType.cs
new file mode 100644
index 0000000..70b7e22
--- /dev/null
+++ b/MapToolkit.Drawing.Topographic/TopoLocationType.cs
@@ -0,0 +1,9 @@
+namespace MapToolkit.Drawing.Topographic
+{
+ public enum TopoLocationType
+ {
+ City,
+ Local,
+ Village
+ }
+}
diff --git a/MapToolkit.Drawing.Topographic/TopoMapData.cs b/MapToolkit.Drawing.Topographic/TopoMapData.cs
new file mode 100644
index 0000000..96aa336
--- /dev/null
+++ b/MapToolkit.Drawing.Topographic/TopoMapData.cs
@@ -0,0 +1,34 @@
+using GeoJSON.Text.Geometry;
+using MapToolkit.DataCells;
+
+namespace MapToolkit.Drawing.Topographic
+{
+ internal class TopoMapData : ITopoMapData
+ {
+ public string Title { get; set; } = "(no name)";
+
+ public Dictionary? Roads { get; set; }
+
+ public Dictionary? Bridges { get; set; }
+
+ public MultiPolygon? ForestPolygons { get; set; }
+
+ public MultiPolygon? RockPolygons { get; set; }
+
+ public MultiPolygon? BuildingPolygons { get; set; }
+
+ public MultiPolygon? WaterPolygons { get; set; }
+
+ public required IDemDataView DemDataCell { get; set; }
+
+ public List? Names { get; set; }
+
+ public List? PlottedPoints { get; set; }
+
+ public List? Icons { get; set; }
+
+ public MultiLineString? Powerlines { get; set; }
+
+ public MultiPolygon? FortPolygons { get; internal set; }
+ }
+}
diff --git a/MapToolkit.Drawing.Topographic/TopoMapDataExtensions.cs b/MapToolkit.Drawing.Topographic/TopoMapDataExtensions.cs
new file mode 100644
index 0000000..be63652
--- /dev/null
+++ b/MapToolkit.Drawing.Topographic/TopoMapDataExtensions.cs
@@ -0,0 +1,24 @@
+namespace MapToolkit.Drawing.Topographic
+{
+ public static class TopoMapDataExtensions
+ {
+ public static ITopoMapData Crop(this ITopoMapData other, Coordinates min, Coordinates max, string? title)
+ {
+ return new TopoMapData()
+ {
+ Title = title ?? other.Title,
+ DemDataCell = other.DemDataCell.CreateView(min, max),
+ ForestPolygons = other.ForestPolygons?.Crop(min, max),
+ RockPolygons = other.RockPolygons?.Crop(min, max),
+ FortPolygons = other.FortPolygons?.Crop(min, max),
+ BuildingPolygons = other.BuildingPolygons?.Crop(min, max),
+ WaterPolygons = other.WaterPolygons?.Crop(min, max),
+ Bridges = other.Bridges?.ToDictionary(k => k.Key, k => k.Value.Crop(min, max)),
+ Roads = other.Roads?.ToDictionary(k => k.Key, k => k.Value.Crop(min, max)),
+ Powerlines = other.Powerlines?.Crop(min, max),
+ Names = other.Names?.Where(n => n.Position?.IsInSquare(min, max) ?? false)?.ToList(),
+ Icons = other.Icons?.Where(n => n.Coordinates?.IsInSquare(min, max) ?? false)?.ToList()
+ };
+ }
+ }
+}
diff --git a/MapToolkit.Drawing.Topographic/TopoMapPathType.cs b/MapToolkit.Drawing.Topographic/TopoMapPathType.cs
new file mode 100644
index 0000000..3999560
--- /dev/null
+++ b/MapToolkit.Drawing.Topographic/TopoMapPathType.cs
@@ -0,0 +1,11 @@
+namespace MapToolkit.Drawing.Topographic
+{
+ public enum TopoMapPathType
+ {
+ Main,
+ Secondary,
+ Road,
+ Track,
+ Trail
+ }
+}
diff --git a/MapToolkit.Drawing.Topographic/TopoMapPdfRender.cs b/MapToolkit.Drawing.Topographic/TopoMapPdfRender.cs
new file mode 100644
index 0000000..5eb1e3e
--- /dev/null
+++ b/MapToolkit.Drawing.Topographic/TopoMapPdfRender.cs
@@ -0,0 +1,275 @@
+using DemUtility;
+using MapToolkit.Drawing;
+using MapToolkit.Drawing.PdfRender;
+using MapToolkit.Projections;
+using PdfSharpCore.Drawing;
+using PdfSharpCore.Pdf;
+using SixLabors.ImageSharp;
+
+namespace MapToolkit.Drawing.Topographic
+{
+ public static class TopoMapPdfRender
+ {
+ private const double LegendWidth = LegendRender.LegendWidthPoints;
+ private const double LegendHeight = LegendRender.LegendHeightPoints;
+ private const double LegendHalfWidth = LegendWidth / 2;
+ private const double Margin = 20;
+
+ private const double LegendWidthWithBothMargin = LegendWidth + 2 * Margin;
+ private const double LegendWidthWithAllsMargins = LegendWidth + 3 * Margin;
+ private const double DoubleLegendWidthWithBothMargin = LegendWidthWithBothMargin * 2;
+
+ public static void RenderPDF(ITopoMapPdfRenderOptions opts, ITopoMapData data, int scale = 25)
+ {
+ var sizeInMeters = new Vector(
+ data.DemDataCell.End.Longitude - data.DemDataCell.Start.Longitude,
+ data.DemDataCell.End.Latitude - data.DemDataCell.Start.Latitude);
+
+ var sizeInPoints = new Vector(
+ sizeInMeters.X / scale * PaperSize.OneMilimeter,
+ sizeInMeters.Y / scale * PaperSize.OneMilimeter);
+
+ if (sizeInPoints.X > PaperSize.ArchEHeight || sizeInPoints.Y > PaperSize.ArchEWidth)
+ {
+ var paperSize = new Vector(PaperSize.ArchEHeight, PaperSize.ArchEWidth);
+ var paperSurface = new Vector(PaperSize.ArchEHeight - (LegendWidth + Margin * 3) - 1, PaperSize.ArchEWidth - Margin * 2);
+
+ var tiles = GetTiles(data.DemDataCell.Start, scale, sizeInMeters, sizeInPoints, paperSurface);
+
+ foreach (var tile in tiles)
+ {
+ var subdata = data.Crop(tile.Min, tile.Max, data.Title + " - " + tile.Name);
+ var file = Path.Combine(opts.TargetDirectory, $"{opts.FileName}_{tile.Name}.pdf");
+ RenderSinglePdf(subdata, scale, paperSize, file, opts, data, tiles, tile);
+ }
+ }
+ else
+ {
+ var paperSize = GetPaperSize(sizeInPoints.X, sizeInPoints.Y);
+ var file = Path.Combine(opts.TargetDirectory, opts.FileName + ".pdf");
+ RenderSinglePdf(data, scale, paperSize, file, opts);
+ }
+ }
+
+ public static void RenderPDFBook(ITopoMapPdfRenderOptions opts, ITopoMapData data, int scale = 25)
+ {
+ var sizeInMeters = new Vector(
+ data.DemDataCell.End.Longitude - data.DemDataCell.Start.Longitude,
+ data.DemDataCell.End.Latitude - data.DemDataCell.Start.Latitude);
+
+ var sizeInPoints = new Vector(
+ sizeInMeters.X / scale * PaperSize.OneMilimeter,
+ sizeInMeters.Y / scale * PaperSize.OneMilimeter);
+
+ var paperSize = new Vector(PaperSize.A3Height, PaperSize.A3Width);
+
+ var paperSurface = new Vector(paperSize.X - Margin * 2, paperSize.Y - Margin * 2);
+
+ var file = Path.Combine(opts.TargetDirectory, opts.FileName + "-book.pdf");
+
+ var tiles = GetTiles(data.DemDataCell.Start, scale, sizeInMeters, sizeInPoints, paperSurface);
+
+ var document = new PdfDocument();
+ document.Info.Title = data.Title;
+ document.Info.Creator = "MapToolkit Topo Map - Print Map created by GrueArbre";
+ document.Info.Author = $"Original Map {opts.Attribution}";
+
+ Console.WriteLine($"{tiles.Count} tiles");
+
+ var page = document.AddPage();
+ page.Width = paperSize.X;
+ page.Height = paperSize.Y;
+
+ var legendTopCenter = new Vector((paperSize.X - LegendWidth - LegendHeight - Margin) / 2, (paperSize.Y - LegendHeight) / 2);
+
+ ToPdfPage(page, legendTopCenter, w => LegendRender.RenderLegend(w, data, opts, scale));
+ ToPdfPage(page, legendTopCenter + new Vector(LegendWidth + Margin, 0), w => LegendRender.DrawMiniMap(w, data, tiles, null, LegendRender.LegendHeight));
+
+ foreach (var tile in tiles.OrderBy(t => t.Name))
+ {
+ var subdata = data.Crop(tile.Min, tile.Max, tile.Name);
+
+ var rdata = TopoMapRenderData.Create(subdata);
+
+ RenderPage(rdata, scale, paperSize, opts, data, tiles, tile, document);
+ }
+ document.Save(file);
+ }
+
+ private static List GetTiles(Coordinates start, int scale, Vector sizeInMeters, Vector sizeInPoints, Vector paperSurface)
+ {
+ var w = Math.Ceiling(sizeInPoints.X / paperSurface.X);
+ var h = Math.Ceiling(sizeInPoints.Y / paperSurface.Y);
+
+ var overlapInMeters = new Vector(
+ (w * paperSurface.X - sizeInPoints.X) / w / PaperSize.OneMilimeter * scale,
+ (h * paperSurface.Y - sizeInPoints.Y) / h / PaperSize.OneMilimeter * scale);
+
+ var baseSize = new Vector(sizeInMeters.X / w, sizeInMeters.Y / h);
+
+ return GetTiles(start, w, h, overlapInMeters, baseSize);
+ }
+
+ private static List GetTiles(Coordinates start, double w, double h, Vector overlapInMeters, Vector baseSize)
+ {
+ var tiles = new List();
+ for (var x = 0; x < w; ++x)
+ {
+ for (var y = 0; y < h; ++y)
+ {
+ var name = $"{(char)('A' + x)}{h - y}";
+ var min = start + new Vector(baseSize.X * x, baseSize.Y * y);
+ var max = start + new Vector(baseSize.X * (x + 1), baseSize.Y * (y + 1));
+ if (x == 0)
+ {
+ max = max + new Vector(overlapInMeters.X, 0);
+ }
+ else if (x == w - 1)
+ {
+ min = min - new Vector(overlapInMeters.X, 0);
+ }
+ else
+ {
+ min = min - new Vector(overlapInMeters.X / 2, 0);
+ max = max + new Vector(overlapInMeters.X / 2, 0);
+ }
+ if (y == 0)
+ {
+ max = max + new Vector(0, overlapInMeters.Y);
+ }
+ else if (y == h - 1)
+ {
+ min = min - new Vector(0, overlapInMeters.Y);
+ }
+ else
+ {
+ min = min - new Vector(0, overlapInMeters.Y / 2);
+ max = max + new Vector(0, overlapInMeters.Y / 2);
+ }
+ tiles.Add(new TopoMapPdfTile(name, min, max));
+ }
+ }
+
+ return tiles;
+ }
+
+ private static void RenderSinglePdf(ITopoMapData data, int scale, Vector paperSize, string file, ITopoMapPdfRenderOptions opts, ITopoMapData? fulldata = null, List? tiles = null, TopoMapPdfTile? current = null)
+ {
+ var document = new PdfDocument();
+ document.Info.Title = data.Title;
+ document.Info.Creator = "MapToolkit Topo Map - Print Map created by GrueArbre";
+ document.Info.Author = $"Original Map {opts.Attribution}";
+
+ var rdata = TopoMapRenderData.Create(data);
+
+ RenderPage(rdata, scale, paperSize, opts, fulldata, tiles, current, document);
+ document.Save(file);
+ }
+
+ private static void RenderPage(TopoMapRenderData rdata, int scale, Vector paperSizeInPoints, ITopoMapPdfRenderOptions opts, ITopoMapData? fulldata, List? tiles, TopoMapPdfTile? current, PdfDocument document)
+ {
+ var page = document.AddPage();
+ page.Width = paperSizeInPoints.X;
+ page.Height = paperSizeInPoints.Y;
+
+ var projSizeInPoints = new Vector(rdata.WidthInMeters / scale * PaperSize.OneMilimeter, rdata.HeightInMeters / scale * PaperSize.OneMilimeter);
+
+ var proj = new NoProjectionArea(rdata.Start, rdata.End, projSizeInPoints / 0.24);
+
+ var dX = paperSizeInPoints.X - projSizeInPoints.X;
+ var dY = paperSizeInPoints.Y - projSizeInPoints.Y;
+
+ Vector mapTopLeft;
+ Vector legendTopCenter;
+ bool miniMap = false;
+ bool drawLegend = true;
+
+ if (dX >= LegendWidthWithAllsMargins && dX < DoubleLegendWidthWithBothMargin)
+ {
+ var delta = (dX - LegendWidthWithAllsMargins) / 2;
+ // | Margin | ... Legend ... | Margin | ... Map ... | Margin |
+ mapTopLeft = new Vector(delta + Margin * 2 + LegendWidth, dY / 2);
+ legendTopCenter = new Vector(delta + LegendHalfWidth + Margin, dY / 2);
+ miniMap = true;
+ }
+ else
+ {
+ mapTopLeft = new Vector(dX / 2, dY / 2);
+ if (dX < DoubleLegendWidthWithBothMargin)
+ {
+ // | ... Map ... |
+ // | | ... Legend ... | Margin |
+ legendTopCenter = new Vector(paperSizeInPoints.X - dX / 2 - LegendHalfWidth - Margin, paperSizeInPoints.Y - dY / 2 - LegendHeight - Margin);
+ drawLegend = paperSizeInPoints.X > PaperSize.A3Height;
+ }
+ else
+ {
+ // | Margin | ... Legend ... | Margin | ... Map ... | Margin | ... Space for legend ...| Margin |
+ legendTopCenter = new Vector(dX / 4, dY / 2);
+ miniMap = true;
+ }
+ }
+
+ ToPdfPage(page, mapTopLeft, w => TopoMapRender.RenderWithExternGraticule(w, rdata, proj));
+ if (drawLegend)
+ {
+ ToPdfPage(page, legendTopCenter - new Vector(LegendHalfWidth, 0), w => LegendRender.RenderLegend(w, rdata.Data, opts, scale));
+ if (miniMap)
+ {
+ ToPdfPage(page, legendTopCenter + new Vector(-LegendHalfWidth, LegendHeight + Margin), w => LegendRender.DrawMiniMap(w, fulldata ?? rdata.Data, tiles, current));
+ }
+ }
+ else
+ {
+ ToPdfPage(page, mapTopLeft, w =>
+ {
+ var style = w.AllocateTextStyle(new[] { "Calibri" }, SixLabors.Fonts.FontStyle.Regular, 30, new SolidColorBrush(Color.Black), null, false, TextAnchor.CenterLeft);
+ w.DrawText(new Vector(0, -30), rdata.Data.Title, style);
+ w.DrawText(new Vector(0, proj.Size.Y + 30), rdata.Data.Title, style);
+
+ style = w.AllocateTextStyle(new[] { "Calibri" }, SixLabors.Fonts.FontStyle.Regular, 30, new SolidColorBrush(Color.Black), null, false, TextAnchor.CenterRight);
+ w.DrawText(new Vector(proj.Size.X, -30), rdata.Data.Title, style);
+ w.DrawText(new Vector(proj.Size.X, proj.Size.Y + 30), rdata.Data.Title, style);
+ });
+ }
+ }
+
+
+
+ private static Vector GetPaperSize(double widthInPoints, double heightInPoints)
+ {
+ if (heightInPoints > PaperSize.A0Width)
+ {
+ // Arch E / Maxmimum size
+ return new Vector(PaperSize.ArchEHeight, PaperSize.ArchEWidth);
+ }
+ if (widthInPoints > PaperSize.A1Height || heightInPoints > PaperSize.A1Width)
+ {
+ // A0
+ return new Vector(PaperSize.A0Height, PaperSize.A0Width);
+ }
+ if (widthInPoints > PaperSize.A2Height || heightInPoints > PaperSize.A2Width)
+ {
+ // A1
+ return new Vector(PaperSize.A1Height, PaperSize.A1Width);
+ }
+ if (widthInPoints > PaperSize.A3Height || heightInPoints > PaperSize.A3Width)
+ {
+ // A2
+ return new Vector(PaperSize.A2Height, PaperSize.A2Width);
+ }
+ // A3
+ return new Vector(PaperSize.A3Height, PaperSize.A3Width);
+ }
+
+
+ public static void ToPdfPage(PdfPage page, Vector shiftInPoints, Action draw)
+ {
+ using (var xgfx = XGraphics.FromPdfPage(page))
+ {
+ xgfx.TranslateTransform(shiftInPoints.X, shiftInPoints.Y);
+ Drawing.Render.ToPdfGraphics(xgfx, PaperSize.OnePixelAt300Dpi, draw);
+ }
+ }
+ }
+}
diff --git a/MapToolkit.Drawing.Topographic/TopoMapPdfTile.cs b/MapToolkit.Drawing.Topographic/TopoMapPdfTile.cs
new file mode 100644
index 0000000..12dfb57
--- /dev/null
+++ b/MapToolkit.Drawing.Topographic/TopoMapPdfTile.cs
@@ -0,0 +1,16 @@
+namespace MapToolkit.Drawing.Topographic
+{
+ internal class TopoMapPdfTile
+ {
+ public TopoMapPdfTile(string name, Coordinates min, Coordinates max)
+ {
+ Name = name;
+ Min = min;
+ Max = max;
+ }
+
+ public string Name { get; }
+ public Coordinates Min { get; }
+ public Coordinates Max { get; }
+ }
+}
\ No newline at end of file
diff --git a/MapToolkit.Drawing.Topographic/TopoMapRender.cs b/MapToolkit.Drawing.Topographic/TopoMapRender.cs
new file mode 100644
index 0000000..95e48fb
--- /dev/null
+++ b/MapToolkit.Drawing.Topographic/TopoMapRender.cs
@@ -0,0 +1,344 @@
+using System.Globalization;
+using GeoJSON.Text.Geometry;
+using MapToolkit.Drawing;
+using MapToolkit.Drawing.Contours;
+using MapToolkit.Projections;
+using SixLabors.ImageSharp;
+
+namespace MapToolkit.Drawing.Topographic
+{
+ public static class TopoMapRender
+ {
+ public static void Render(IDrawSurface writer, TopoMapRenderData renderData, NoProjectionArea proj)
+ {
+ var style = TopoMapStyle.CreateFull(writer, ColorPalette.Default);
+
+ RenderAny(writer, renderData, proj, style);
+
+ NaiveGraticule(writer, proj, style);
+ }
+
+ public static void RenderWithExternGraticule(IDrawSurface writer, TopoMapRenderData renderData, NoProjectionArea proj)
+ {
+ var style = TopoMapStyle.CreateFull(writer, ColorPalette.Default, true);
+
+ RenderAny(writer, renderData, proj, style);
+
+ NaiveGraticule(writer, proj, style);
+ }
+
+ public static void RenderLod2(IDrawSurface writer, TopoMapRenderData renderData, NoProjectionArea proj)
+ {
+ var style = TopoMapStyle.CreateLod2(writer, ColorPalette.Default);
+
+ RenderAny(writer, renderData, proj, style, true);
+
+ NaiveGraticule(writer, proj, style);
+ }
+
+ public static void RenderLod3(IDrawSurface writer, TopoMapRenderData renderData, NoProjectionArea proj)
+ {
+ var style = TopoMapStyle.CreateLod3(writer, ColorPalette.Default);
+ var data = renderData.Data;
+
+ if (data.ForestPolygons != null)
+ {
+ DrawPolygons(writer, proj, data.ForestPolygons, style.forest);
+ }
+
+ if (data.RockPolygons != null)
+ {
+ DrawPolygons(writer, proj, data.RockPolygons, style.rocks);
+ }
+
+ if (renderData.Img != null)
+ {
+ writer.DrawImage(renderData.Img, Vector.Zero, proj.Size, 0.5);
+ }
+
+ if (data.WaterPolygons != null)
+ {
+ DrawPolygons(writer, proj, data.WaterPolygons, style.water);
+ }
+
+ if (data.Roads != null)
+ {
+ RenderRoads(writer, data.Roads, proj, style.roadForeground, style.roadBackground);
+ }
+
+ if (data.Bridges != null)
+ {
+ RenderBridgesRoads(writer, data.Bridges, proj, style.bridgeForeground, style.bridgeBackground, style.BridgeLimit);
+ }
+
+ RenderNames(writer, data, proj, style);
+
+ NaiveGraticule(writer, proj, style, 10000);
+ }
+
+ private static void RenderAny(IDrawSurface writer, TopoMapRenderData renderData, NoProjectionArea proj, TopoMapStyle style, bool simpler = false)
+ {
+ var data = renderData.Data;
+
+ if (data.ForestPolygons != null)
+ {
+ DrawPolygons(writer, proj, data.ForestPolygons, style.forest);
+ }
+ if (data.RockPolygons != null)
+ {
+ DrawPolygons(writer, proj, data.RockPolygons, style.rocks);
+ }
+
+ var render = new ContourRender(writer, style.contourStyle);
+ if (simpler)
+ {
+ render.RenderSimpler(renderData.Contour, proj, renderData.Img);
+ }
+ else
+ {
+ render.Render(renderData.Contour, proj, renderData.Img);
+ }
+
+ if (data.WaterPolygons != null)
+ {
+ DrawPolygons(writer, proj, data.WaterPolygons, style.water);
+ }
+
+ if (data.BuildingPolygons != null && style.buildings != null)
+ {
+ DrawPolygons(writer, proj, data.BuildingPolygons, style.buildings);
+ }
+
+ if (data.Roads != null)
+ {
+ RenderRoads(writer, data.Roads, proj, style.roadForeground, style.roadBackground);
+ }
+
+ if (data.FortPolygons != null && style.Forts != null)
+ {
+ DrawPolygons(writer, proj, data.FortPolygons, style.Forts);
+ }
+
+ if (data.Bridges != null)
+ {
+ RenderBridgesRoads(writer, data.Bridges, proj, style.bridgeForeground, style.bridgeBackground, style.BridgeLimit);
+ }
+
+ if (data.Powerlines != null && style.Powerline != null)
+ {
+ foreach (var pl in data.Powerlines.Coordinates)
+ {
+ writer.DrawPolyline(pl.Coordinates.Select(p => proj.Project(p)), style.Powerline);
+ }
+ }
+
+ if (renderData.PlottedPoints != null && style.plotted != null && style.plottedCircle != null)
+ {
+ RenderPlotted(writer, proj, renderData.PlottedPoints, style.plotted, style.plottedCircle);
+ }
+
+ if (data.Icons != null)
+ {
+ foreach (var icon in data.Icons)
+ {
+ var i = GetIcon(style, icon.MapType);
+ if (i != null)
+ {
+ writer.DrawIcon(proj.Project(icon.Coordinates), i);
+ }
+ }
+ }
+
+ RenderNames(writer, data, proj, style);
+ }
+
+ private static void RenderNames(IDrawSurface writer, ITopoMapData data, NoProjectionArea proj, TopoMapStyle style)
+ {
+ if (data.Names != null)
+ {
+ foreach (var entry in data.Names)
+ {
+ var pos = proj.Project(entry.Position);
+ var nstyle = style.name;
+ if (entry.Type == TopoLocationType.City)
+ {
+ nstyle = style.city;
+ }
+ else if (entry.Type == TopoLocationType.Local)
+ {
+ nstyle = style.local;
+ }
+ if (nstyle != null)
+ {
+ writer.DrawText(pos, entry.Name, nstyle);
+ }
+ }
+ }
+ }
+
+ private static IDrawIcon? GetIcon(TopoMapStyle style, TopoIconType mapType)
+ {
+ switch (mapType)
+ {
+ case TopoIconType.WindPowerPlant:
+ return style.WindPowerPlant;
+
+ case TopoIconType.WaterTower:
+ return style.WaterTower;
+
+ case TopoIconType.Transmitter:
+ return style.Transmitter;
+
+ case TopoIconType.ElectricityPylon:
+ return style.Dot;
+
+ case TopoIconType.Hospital:
+ return style.Hospital;
+ }
+ return null;
+ }
+
+ private static void RenderPlotted(IDrawSurface writer, NoProjectionArea proj, List plottedPoints, IDrawTextStyle plotted, IDrawStyle plottedCircle)
+ {
+ foreach (var point in plottedPoints)
+ {
+ var projected = proj.Project(point.Coordinates);
+ var text = Math.Round(point.Elevation).ToString(CultureInfo.InvariantCulture);
+
+ writer.DrawCircle(projected, 1.5f, plottedCircle);
+ writer.DrawText(projected + new Vector(3, 0), text, plotted);
+
+ }
+ }
+
+ private static void NaiveGraticule(IDrawSurface writer, NoProjectionArea proj, TopoMapStyle style, int step = 1000)
+ {
+ var format = "00";
+ if (step == 10000)
+ {
+ format = "0";
+ }
+ for (int x = 1; x < 100; ++x)
+ {
+ var projected = proj.Project(new Coordinates(0, x * step));
+ if (projected.X >= 0 && projected.X <= proj.Size.X)
+ {
+ writer.DrawPolyline(new[] {
+ new Vector(projected.X, 0),
+ new Vector(projected.X, proj.Size.Y),
+ }, x % 10 == 0 ? style.gl2 : style.gl1);
+
+ writer.DrawText(new Vector(projected.X, 0), x.ToString(format), style.gltT);
+ writer.DrawText(new Vector(projected.X, proj.Size.Y), x.ToString(format), style.gltB);
+ }
+ }
+ for (int y = 1; y < 100; ++y)
+ {
+ var projected = proj.Project(new Coordinates(y * step, 0));
+ if (projected.Y >= 0 && projected.Y <= proj.Size.Y)
+ {
+ writer.DrawPolyline(new[] {
+ new Vector(0, projected.Y),
+ new Vector(proj.Size.X, projected.Y),
+ }, y % 10 == 0 ? style.gl2 : style.gl1);
+
+ writer.DrawText(new Vector(0, projected.Y), y.ToString(format), style.gltL);
+ writer.DrawText(new Vector(proj.Size.X, projected.Y), y.ToString(format), style.gltR);
+ }
+ }
+ }
+
+ private static void RenderRoads(IDrawSurface writer, Dictionary data, NoProjectionArea proj, IDrawStyle?[] foreground, IDrawStyle?[] background)
+ {
+ foreach (var roads in data)
+ {
+ var style = background[(int)roads.Key];
+ if (style != null)
+ {
+ foreach (var road in roads.Value.Coordinates)
+ {
+ writer.DrawPolyline(road.Coordinates.Select(p => proj.Project(p)), style);
+ }
+ }
+ }
+ foreach (var roads in data.OrderByDescending(t => t.Key))
+ {
+ var style = foreground[(int)roads.Key];
+ if (style != null)
+ {
+ foreach (var road in roads.Value.Coordinates)
+ {
+ writer.DrawPolyline(road.Coordinates.Select(p => proj.Project(p)), style);
+ }
+ }
+ }
+ }
+
+ private static void RenderBridgesRoads(IDrawSurface writer, Dictionary data, NoProjectionArea proj, IDrawStyle?[] foreground, IDrawStyle?[] background, IDrawStyle limitLine)
+ {
+ foreach (var roads in data)
+ {
+ var style = background[(int)roads.Key];
+ if (style != null)
+ {
+ foreach (var road in roads.Value.Coordinates)
+ {
+ writer.DrawPolyline(road.Coordinates.Select(p => proj.Project(p)), style);
+
+ BridgeLimit(writer, proj.Project(road.Coordinates[0]), proj.Project(road.Coordinates[1]), limitLine);
+ BridgeLimit(writer, proj.Project(road.Coordinates[road.Coordinates.Count - 1]), proj.Project(road.Coordinates[road.Coordinates.Count - 2]), limitLine);
+ }
+ }
+ }
+ foreach (var roads in data.OrderByDescending(t => t.Key))
+ {
+ var style = foreground[(int)roads.Key];
+ if (style != null)
+ {
+ foreach (var road in roads.Value.Coordinates)
+ {
+ writer.DrawPolyline(road.Coordinates.Select(p => proj.Project(p)), style);
+ }
+ }
+ }
+ }
+
+ internal static void BridgeLimit(IDrawSurface writer, Vector begin, Vector inside, IDrawStyle limitLine)
+ {
+ var delta = System.Numerics.Vector2.Normalize((inside - begin).ToFloat());
+ var d8 = delta * 10;
+ var d4 = delta * 4.5f;
+ var x1 = System.Numerics.Vector2.Transform(d8, System.Numerics.Matrix3x2.CreateRotation((float)(3 * Math.PI / 4)));
+ var x2 = System.Numerics.Vector2.Transform(d8, System.Numerics.Matrix3x2.CreateRotation((float)(-3 * Math.PI / 4)));
+ writer.DrawPolyline(new[] { begin + new Vector(d4), begin + new Vector(x1 + d4) }, limitLine);
+ writer.DrawPolyline(new[] { begin + new Vector(d4), begin + new Vector(x2 + d4) }, limitLine);
+ }
+
+ private static void DrawPolygons(IDrawSurface writer, NoProjectionArea proj, MultiPolygon surface, IDrawStyle styleToUse)
+ {
+ foreach (var poly in surface.Coordinates)
+ {
+ writer.DrawPolygon(
+ poly.Coordinates[0].Coordinates.Cast().Select(proj.Project),
+ poly.Coordinates.Skip(1).Select(l => l.Coordinates.Cast().Select(proj.Project)),
+ styleToUse);
+ }
+ }
+
+ internal static void DrawPolygonsSimplified(IDrawSurface writer, NoProjectionArea proj, MultiPolygon surface, IDrawStyle styleToUse)
+ {
+ foreach (var poly in surface.Coordinates)
+ {
+ var contour = LevelOfDetailHelper.SimplifyAnglesAndDistancesClosed(poly.Coordinates[0].Coordinates.Cast().Select(proj.Project), 1);
+
+ if (contour.Count > 0)
+ {
+ writer.DrawPolygon(
+ contour,
+ LevelOfDetailHelper.SimplifyAnglesAndDistancesClosed(poly.Coordinates.Skip(1).Select(l => l.Coordinates.Cast().Select(proj.Project)), 1) ?? Enumerable.Empty>(),
+ styleToUse);
+ }
+ }
+ }
+ }
+}
diff --git a/MapToolkit.Drawing.Topographic/TopoMapRenderData.cs b/MapToolkit.Drawing.Topographic/TopoMapRenderData.cs
new file mode 100644
index 0000000..2fa71f7
--- /dev/null
+++ b/MapToolkit.Drawing.Topographic/TopoMapRenderData.cs
@@ -0,0 +1,62 @@
+using MapToolkit.Contours;
+using MapToolkit.DataCells;
+using MapToolkit.GeodeticSystems;
+using MapToolkit.Hillshading;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace MapToolkit.Drawing.Topographic
+{
+ public sealed class TopoMapRenderData
+ {
+ private TopoMapRenderData(ITopoMapData data, Image img, ContourGraph contour, List plotted)
+ {
+ Data = data;
+ Img = img;
+ Contour = contour;
+ PlottedPoints = plotted;
+ }
+
+ public ITopoMapData Data { get; }
+
+ public Image Img { get; }
+
+ public ContourGraph Contour { get; }
+
+ public List PlottedPoints { get; }
+
+ public double WidthInMeters => End.Longitude - Start.Longitude;
+
+ public double HeightInMeters => End.Latitude - Start.Latitude;
+
+ public Coordinates End => Data.DemDataCell.End;
+
+ public Coordinates Start => Data.DemDataCell.Start;
+
+ public static TopoMapRenderData Create(ITopoMapData data)
+ {
+ var img = new HillshaderFast(new Vector(10, 10)).GetPixelsAlphaBelowFlat(data.DemDataCell);
+
+ var contour = new ContourGraph();
+ contour.Add(data.DemDataCell, new ContourLevelGenerator(10, 10), false);
+
+ var plotted = ComputePlottedPoints(data.DemDataCell, contour);
+
+ return new TopoMapRenderData(data, img, contour, plotted);
+ }
+
+
+ internal static List ComputePlottedPoints(IDemDataView demView, ContourGraph contour)
+ {
+ var lines = contour.Lines.Where(l => l.IsClosed && LengthInMeters(l.Points) > 200);
+ var plotted = ContourMaximaMinima.FindMaxima(demView, lines);
+ plotted.AddRange(ContourMaximaMinima.FindMinima(demView, lines));
+ return plotted;
+ }
+
+ private static double LengthInMeters(List points)
+ {
+ return points.Take(points.Count - 1).Zip(points.Skip(1), (a, b) => MeterProjectedDistance.Instance.DistanceInMeters(a, b)).Sum();
+ }
+ }
+}
diff --git a/MapToolkit.Drawing.Topographic/TopoMapStyle.cs b/MapToolkit.Drawing.Topographic/TopoMapStyle.cs
new file mode 100644
index 0000000..88f16be
--- /dev/null
+++ b/MapToolkit.Drawing.Topographic/TopoMapStyle.cs
@@ -0,0 +1,209 @@
+using MapToolkit.Drawing;
+using MapToolkit.Drawing.Contours;
+using SixLabors.ImageSharp;
+
+namespace MapToolkit.Drawing.Topographic
+{
+ internal class TopoMapStyle
+ {
+ public IDrawIcon? WindPowerPlant { get; private set; }
+ public required IDrawStyle rocks { get; internal set; }
+ public required IDrawStyle forest { get; internal set; }
+ public required IDrawStyle water { get; internal set; }
+ public IDrawStyle? buildings { get; private set; }
+ public required ContourRenderStyle contourStyle { get; internal set; }
+ public required IDrawStyle?[] roadForeground { get; internal set; }
+ public required IDrawStyle?[] roadBackground { get; internal set; }
+ public required IDrawStyle?[] bridgeForeground { get; internal set; }
+ public required IDrawStyle?[] bridgeBackground { get; internal set; }
+ public required IDrawStyle gl1 { get; internal set; }
+ public required IDrawStyle gl2 { get; internal set; }
+ public required IDrawTextStyle gltT { get; internal set; }
+ public required IDrawTextStyle gltB { get; internal set; }
+ public required IDrawTextStyle gltL { get; internal set; }
+ public required IDrawTextStyle gltR { get; internal set; }
+ public required IDrawTextStyle city { get; internal set; }
+ public IDrawTextStyle? local { get; private set; }
+ public IDrawTextStyle? name { get; private set; }
+ public IDrawTextStyle? plotted { get; private set; }
+ public IDrawStyle? plottedCircle { get; private set; }
+ public IDrawIcon? WaterTower { get; private set; }
+ public IDrawIcon? TechnicalTower { get; internal set; }
+ public IDrawIcon? Transmitter { get; private set; }
+ public IDrawStyle? Powerline { get; private set; }
+ public IDrawIcon? Dot { get; private set; }
+ public IDrawIcon? Hospital { get; private set; }
+ public required IDrawStyle BridgeLimit { get; internal set; }
+ public IDrawStyle? Forts { get; internal set; }
+
+ public static TopoMapStyle CreateFull(IDrawSurface writer, ColorPalette palette, bool externGraticule = false)
+ {
+ var rocksFill = writer.AllocateBrushStyle(palette.RocksSymbol);
+ var rocks = writer.AllocateStyle(new VectorBrush(writer, 20, 20, d =>
+ {
+ d.DrawCircle(new Vector(5, 5), 3, rocksFill);
+ d.DrawCircle(new Vector(15, 15), 3, rocksFill);
+ }), new Pen(new SolidColorBrush(palette.RocksBorder)));
+
+ var forest = writer.AllocateStyle(palette.ForestFill, palette.ForestBorder);
+ var water = writer.AllocateStyle(palette.WaterFill, palette.WaterBorder);
+ var buildings = writer.AllocateStyle(palette.BuildingFill, palette.BuildingBorder);
+
+ var contourStyle = new ContourRenderStyle(writer);
+
+ var r1 = writer.AllocatePenStyle(palette.MainRoad, 5);
+ var b1 = writer.AllocatePenStyle(palette.RoadBorder, 6);
+ var bx1 = writer.AllocatePenStyle(palette.RoadBorder, 10);
+ var r2 = writer.AllocatePenStyle(palette.SecondaryRoad, 4);
+ var b2 = writer.AllocatePenStyle(palette.RoadBorder, 5);
+ var bx2 = writer.AllocatePenStyle(palette.RoadBorder, 9);
+ var r3 = writer.AllocatePenStyle(palette.Road, 4);
+ var b4 = writer.AllocatePenStyle(palette.RoadBorder, 5, new[] { 10.0, 6.0 });
+ var r7 = writer.AllocatePenStyle(palette.Trail, 5);
+
+ var gl1 = writer.AllocatePenStyle(palette.Graticule, 1);
+ var gl2 = writer.AllocatePenStyle(palette.Graticule, 2);
+ var gltT = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Bold, 28, new SolidColorBrush(palette.Graticule), new Pen(new SolidColorBrush(palette.TextBorder), 3), true, TextAnchor.TopCenter);
+ var gltB = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Bold, 28, new SolidColorBrush(palette.Graticule), new Pen(new SolidColorBrush(palette.TextBorder), 3), true, TextAnchor.BottomCenter);
+ var gltL = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Bold, 28, new SolidColorBrush(palette.Graticule), new Pen(new SolidColorBrush(palette.TextBorder), 3), true, TextAnchor.CenterLeft);
+ var gltR = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Bold, 28, new SolidColorBrush(palette.Graticule), new Pen(new SolidColorBrush(palette.TextBorder), 3), true, TextAnchor.CenterRight);
+
+ var city = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Italic, 32, new SolidColorBrush(Color.Black), new Pen(new SolidColorBrush(palette.TextBorder), 3), true);
+ var local = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.BoldItalic, 26, new SolidColorBrush(Color.Black), new Pen(new SolidColorBrush(palette.TextBorder), 3), true);
+ var name = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Bold, 22, new SolidColorBrush(Color.Black), new Pen(new SolidColorBrush(palette.TextBorder), 3), true);
+
+ return new TopoMapStyle()
+ {
+ rocks = rocks,
+ forest = forest,
+ buildings = buildings,
+ water = water,
+ contourStyle = contourStyle,
+ roadForeground = new[] { r1, r2, r3, r3, r7 },
+ roadBackground = new[] { b1, b2, b2, b4, null },
+ bridgeForeground = new[] { r1, r2, r3, r3, r7 },
+ bridgeBackground = new[] { bx1, bx2, bx2, bx2, null },
+ gl1 = gl1,
+ gl2 = gl2,
+ gltT = externGraticule ? gltB : gltT,
+ gltB = externGraticule ? gltT : gltB,
+ gltL = externGraticule ? gltR : gltL,
+ gltR = externGraticule ? gltL : gltR,
+ city = city,
+ local = local,
+ name = name,
+ plotted = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Italic, 18, new SolidColorBrush(Color.Black), null, false, TextAnchor.CenterLeft),
+ plottedCircle = writer.AllocateBrushStyle(Color.Black),
+ WindPowerPlant = IconsRender.WindTurbine(writer),
+ WaterTower = IconsRender.WaterTower(writer),
+ //TechnicalTower = IconsRender.TechnicalTower(writer),
+ Transmitter = IconsRender.Transmitter(writer),
+ Powerline = writer.AllocatePenStyle(Color.Black),
+ Dot = IconsRender.Dot(writer),
+ Hospital = IconsRender.Hospital(writer),
+ BridgeLimit = writer.AllocatePenStyle(palette.RoadBorder, 3),
+ Forts = writer.AllocateStyle(Color.White, Color.Black)
+ };
+ }
+
+
+ public static TopoMapStyle CreateLod2(IDrawSurface writer, ColorPalette palette)
+ {
+ var rocks = writer.AllocateBrushStyle(palette.RocksFill);
+ var forest = writer.AllocateBrushStyle(palette.ForestFill);
+ var water = writer.AllocateBrushStyle(palette.WaterFill);
+
+ var contourStyle = new ContourRenderStyle(writer);
+
+ var r1 = writer.AllocatePenStyle(palette.MainRoad, 5);
+ var b1 = writer.AllocatePenStyle(Color.Black, 6);
+ var bx1 = writer.AllocatePenStyle(Color.Black, 10);
+ var r2 = writer.AllocatePenStyle(palette.SecondaryRoad, 4);
+ var b2 = writer.AllocatePenStyle(Color.Black, 5);
+ var bx2 = writer.AllocatePenStyle(Color.Black, 9);
+ var r3 = writer.AllocatePenStyle(Color.White, 4);
+
+ var gl1 = writer.AllocatePenStyle(palette.Graticule, 1);
+ var gl2 = writer.AllocatePenStyle(palette.Graticule, 2);
+ var gltT = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Bold, 28 * 4, new SolidColorBrush(palette.Graticule), new Pen(new SolidColorBrush(palette.TextBorder), 3), true, TextAnchor.TopCenter);
+ var gltB = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Bold, 28 * 4, new SolidColorBrush(palette.Graticule), new Pen(new SolidColorBrush(palette.TextBorder), 3), true, TextAnchor.BottomCenter);
+ var gltL = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Bold, 28 * 4, new SolidColorBrush(palette.Graticule), new Pen(new SolidColorBrush(palette.TextBorder), 3), true, TextAnchor.CenterLeft);
+ var gltR = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Bold, 28 * 4, new SolidColorBrush(palette.Graticule), new Pen(new SolidColorBrush(palette.TextBorder), 3), true, TextAnchor.CenterRight);
+
+ var city = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Italic, 32 * 4, new SolidColorBrush(Color.Black), new Pen(new SolidColorBrush(palette.TextBorder), 3), true);
+ var local = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.BoldItalic, 26 * 4, new SolidColorBrush(Color.Black), new Pen(new SolidColorBrush(palette.TextBorder), 3), true);
+
+
+ return new TopoMapStyle()
+ {
+ rocks = rocks,
+ forest = forest,
+ buildings = null,
+ water = water,
+ contourStyle = contourStyle,
+ roadForeground = new[] { r1, r2, r3, null, null },
+ roadBackground = new[] { b1, b2, b2, null, null },
+ bridgeForeground = new[] { r1, r2, r3, null, null },
+ bridgeBackground = new[] { bx1, bx2, bx2, null, null },
+ gl1 = gl1,
+ gl2 = gl2,
+ gltT = gltT,
+ gltB = gltB,
+ gltL = gltL,
+ gltR = gltR,
+ city = city,
+ local = local,
+ name = null,
+ BridgeLimit = writer.AllocatePenStyle(palette.RoadBorder, 3)
+ };
+ }
+
+ public static TopoMapStyle CreateLod3(IDrawSurface writer, ColorPalette palette)
+ {
+ var rocks = writer.AllocateBrushStyle(palette.RocksFill);
+ var forest = writer.AllocateBrushStyle(palette.ForestFill);
+ var water = writer.AllocateBrushStyle(palette.WaterFill);
+
+ var contourStyle = new ContourRenderStyle(writer);
+
+ var r1 = writer.AllocatePenStyle(palette.MainRoad, 5);
+ var b1 = writer.AllocatePenStyle(Color.Black, 6);
+ var bx1 = writer.AllocatePenStyle(Color.Black, 10);
+ var r2 = writer.AllocatePenStyle(palette.SecondaryRoad, 4);
+ var b2 = writer.AllocatePenStyle(Color.Black, 5);
+ var bx2 = writer.AllocatePenStyle(Color.Black, 9);
+
+ var gl1 = writer.AllocatePenStyle(palette.Graticule, 1);
+ var gl2 = writer.AllocatePenStyle(palette.Graticule, 2);
+ var gltT = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Bold, 28 * 16, new SolidColorBrush(palette.Graticule), new Pen(new SolidColorBrush(palette.TextBorder), 3), true, TextAnchor.TopCenter);
+ var gltB = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Bold, 28 * 16, new SolidColorBrush(palette.Graticule), new Pen(new SolidColorBrush(palette.TextBorder), 3), true, TextAnchor.BottomCenter);
+ var gltL = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Bold, 28 * 16, new SolidColorBrush(palette.Graticule), new Pen(new SolidColorBrush(palette.TextBorder), 3), true, TextAnchor.CenterLeft);
+ var gltR = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Bold, 28 * 16, new SolidColorBrush(palette.Graticule), new Pen(new SolidColorBrush(palette.TextBorder), 3), true, TextAnchor.CenterRight);
+
+ var city = writer.AllocateTextStyle(new[] { "Calibri", "sans-serif" }, SixLabors.Fonts.FontStyle.Italic, 32 * 16, new SolidColorBrush(Color.Black), new Pen(new SolidColorBrush(palette.TextBorder), 3), true);
+
+ return new TopoMapStyle()
+ {
+ rocks = rocks,
+ forest = forest,
+ buildings = null,
+ water = water,
+ contourStyle = contourStyle,
+ roadForeground = new[] { r1, r2, null, null, null },
+ roadBackground = new[] { b1, b2, null, null, null },
+ bridgeForeground = new[] { r1, r2, null, null, null },
+ bridgeBackground = new[] { bx1, bx2, null, null, null },
+ gl1 = gl1,
+ gl2 = gl2,
+ gltT = gltT,
+ gltB = gltB,
+ gltL = gltL,
+ gltR = gltR,
+ city = city,
+ local = null,
+ name = null,
+ BridgeLimit = writer.AllocatePenStyle(palette.RoadBorder, 3)
+ };
+ }
+ }
+}
diff --git a/MapToolkit.Drawing/MapToolkit.Drawing.csproj b/MapToolkit.Drawing/MapToolkit.Drawing.csproj
index 09615e2..1038651 100644
--- a/MapToolkit.Drawing/MapToolkit.Drawing.csproj
+++ b/MapToolkit.Drawing/MapToolkit.Drawing.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net8.0
enable
enable
diff --git a/MapToolkit.Test/MapToolkit.Test.csproj b/MapToolkit.Test/MapToolkit.Test.csproj
index 8ad48b6..6d1db94 100644
--- a/MapToolkit.Test/MapToolkit.Test.csproj
+++ b/MapToolkit.Test/MapToolkit.Test.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net8.0
enable
false
diff --git a/MapToolkit.sln b/MapToolkit.sln
index 873cef1..9ed4cb5 100644
--- a/MapToolkit.sln
+++ b/MapToolkit.sln
@@ -9,9 +9,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemUtility", "DemUtility\De
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MapToolkit.Test", "MapToolkit.Test\MapToolkit.Test.csproj", "{F797D011-4BE7-435F-9F5F-0E52140B787A}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapToolkit.Drawing", "MapToolkit.Drawing\MapToolkit.Drawing.csproj", "{CD89FBC4-5213-4908-9B77-421C73CFFB6D}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MapToolkit.Drawing", "MapToolkit.Drawing\MapToolkit.Drawing.csproj", "{CD89FBC4-5213-4908-9B77-421C73CFFB6D}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapToolkit.Drawing.Test", "MapToolkit.Drawing.Test\MapToolkit.Drawing.Test.csproj", "{789FFCD4-375D-4201-BCEA-776B3A8D2EAB}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MapToolkit.Drawing.Test", "MapToolkit.Drawing.Test\MapToolkit.Drawing.Test.csproj", "{789FFCD4-375D-4201-BCEA-776B3A8D2EAB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapToolkit.Drawing.Topographic", "MapToolkit.Drawing.Topographic\MapToolkit.Drawing.Topographic.csproj", "{A4A7588A-0141-4D37-A614-4E889006CF1F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -39,6 +41,10 @@ Global
{789FFCD4-375D-4201-BCEA-776B3A8D2EAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{789FFCD4-375D-4201-BCEA-776B3A8D2EAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{789FFCD4-375D-4201-BCEA-776B3A8D2EAB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A4A7588A-0141-4D37-A614-4E889006CF1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A4A7588A-0141-4D37-A614-4E889006CF1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A4A7588A-0141-4D37-A614-4E889006CF1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A4A7588A-0141-4D37-A614-4E889006CF1F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/MapToolkit/MapToolkit.csproj b/MapToolkit/MapToolkit.csproj
index 550bd3e..ff7ab34 100644
--- a/MapToolkit/MapToolkit.csproj
+++ b/MapToolkit/MapToolkit.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net8.0
enable
True
https://github.com/jetelain/mapkit