Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge C# & OSM task manager implementation #72

Draft
wants to merge 56 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
d26f5f8
Initial version of OsmGursBuildingImport
DavidKarlas Sep 5, 2021
05a4fdf
Switch from blob to azure functions URL
DavidKarlas Sep 11, 2021
c556bb8
Switch from using Overpass to downloaded .original.osm file
DavidKarlas Sep 11, 2021
2aed071
Switch from osmosis to osmium for generating extracts
DavidKarlas Sep 11, 2021
4ecb9a3
Fix "addr:village" being set for Ljubljana
DavidKarlas Sep 12, 2021
d292038
Stop emitting fixme tags on casing change
DavidKarlas Sep 12, 2021
b6c3342
Move TaskManager .json creation logic to new file
DavidKarlas Sep 12, 2021
8c42527
Do LinearRing->Polygon conversion sooner
DavidKarlas Sep 12, 2021
0ffed7c
Async main
DavidKarlas Sep 12, 2021
adf1437
Switch to .bz2 and fix some issues with osmium poly generation
DavidKarlas Sep 14, 2021
9cc6683
Start handling existing addresses set on nodes
DavidKarlas Sep 14, 2021
4b8693c
Always load GURS-DOF025 even if area intersects with Ljubljana
DavidKarlas Sep 14, 2021
f90f75c
Fix bug where `ref:gurs:sta_sid` was not applied on matched buildings
DavidKarlas Sep 14, 2021
d41f1a0
Added fixme for big buildings.
DavidKarlas Sep 18, 2021
5d54fd4
Match buildings by sta_sid
DavidKarlas Sep 18, 2021
bebd26b
Don't error if no data/temp/ folder yet and don't error on first run …
DavidKarlas Sep 18, 2021
d1714dd
Stop ugly workaround for VSMac
DavidKarlas Sep 18, 2021
4c75a3f
Minor changes to output folder
DavidKarlas Sep 18, 2021
ce39d09
Initial version of script that does it all
DavidKarlas Sep 18, 2021
e729d91
Remove `layer_locked=true` from full.osm
DavidKarlas Sep 19, 2021
2d3ed4f
If Street has ID=0 it means village doesn't have streets, which means…
DavidKarlas Sep 27, 2021
a687e8e
Reworked logic from being CLI tool that produces bunch of files into …
DavidKarlas Oct 5, 2021
cc78185
Fixed problem with NTS coming from MyGet and made paths absolute
DavidKarlas Oct 5, 2021
5cb55f8
Switch to http, to make things easier, JOSM doesn't mind
DavidKarlas Oct 9, 2021
7a21062
Fixed bug where .poly files were not written out fully
DavidKarlas Oct 9, 2021
ca68924
Switch to using local `osmx extract`
DavidKarlas Oct 9, 2021
16cfa2e
Use XML instead of PBF, to get file bounds for free
DavidKarlas Oct 9, 2021
ce8f097
Add Source=GURS and comment to changeset
DavidKarlas Oct 9, 2021
3751f81
Fix bug where old temp files were left behind and program couldn't de…
DavidKarlas Nov 3, 2021
77f2ad7
Make poly generation more robust...
DavidKarlas Nov 3, 2021
58795cb
Remove all `addr:place`
DavidKarlas Nov 3, 2021
1fc3807
Also remove hamlet
DavidKarlas Nov 3, 2021
cb2e9c4
Properly handle dual language situation
DavidKarlas Dec 24, 2021
d2ab897
Fix bug where elements that were just modified(tags changed) are not …
DavidKarlas Jan 6, 2022
b89be25
Whops small typo...
DavidKarlas Jan 6, 2022
8d7548c
Removed fixme for big buildings
DavidKarlas Jan 15, 2022
700abb5
Fix relationships not having `type`=`multipolygon`
DavidKarlas May 14, 2022
028a49a
Switch from picking up VotingAreas each time from latest data to fixe…
DavidKarlas Sep 16, 2022
0d5d1eb
Limit to just LV units, otherwise we have duplicated keys...
DavidKarlas Sep 16, 2022
14dcfe9
OSMExpress really doesn't like some .poly files
DavidKarlas Sep 18, 2022
e1ece23
I guess fixer can make Multipolygon out of Polygon...
DavidKarlas Sep 18, 2022
d2fb9b9
Migrate from old GURS data to new format
DavidKarlas Jul 3, 2023
4f446b3
Switch back to old IDs(without huge 1002000 in front
DavidKarlas Jul 3, 2023
093ebc0
Switch from using GEOM column to E and N columns
DavidKarlas Aug 12, 2023
61bae1e
Use latest 2022 orthophoto available in Ljubljana
stefanb Apr 19, 2023
5f8b6e5
Remove unused createBuildings.sh and getFilteredPbf.sh and revert cha…
DavidKarlas Aug 19, 2023
8a6fa80
Naselja from the same revision as VLV.geojson
stefanb Nov 24, 2023
22330cc
New Areas
DavidKarlas Nov 25, 2023
6d539be
Fix VLV path
DavidKarlas Nov 25, 2023
e77cac9
Progress and write exceptions
DavidKarlas Nov 25, 2023
1de80c0
Don't change `addr:housenumber` from 22b to 22B if casing is only dif…
DavidKarlas Nov 26, 2023
50ccc64
Merge pull request #3 from stefanb/razrez-po-naseljih
DavidKarlas Nov 26, 2023
52baf09
Fix attribute change fixme to show correct from value
DavidKarlas Nov 29, 2023
f953453
If street name is empty put settlement name instead
DavidKarlas Mar 11, 2024
a50dedc
Try catch ignore if some weird relation is problematic
DavidKarlas Mar 12, 2024
21a8f6c
No comment
DavidKarlas Jun 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
407 changes: 407 additions & 0 deletions .gitignore

Large diffs are not rendered by default.

46 changes: 46 additions & 0 deletions OsmGursBuildingImport/D96Converter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using NetTopologySuite.Geometries;
using ProjNet;
using ProjNet.CoordinateSystems;
using ProjNet.CoordinateSystems.Transformations;

namespace OsmGursBuildingImport
{
internal sealed class D96Converter : ICoordinateSequenceFilter
{
public static D96Converter Instance { get; } = new D96Converter();

private readonly MathTransform _mathTransform = new CoordinateSystemServices().CreateTransformation(new CoordinateSystemFactory().CreateFromWkt(@"PROJCS[""Slovenia 1996 / Slovene National Grid"",
GEOGCS[""Slovenia 1996"",
DATUM[""Slovenia_Geodetic_Datum_1996"",
SPHEROID[""GRS 1980"",6378137,298.257222101,
AUTHORITY[""EPSG"",""7019""]],
TOWGS84[0,0,0,0,0,0,0],
AUTHORITY[""EPSG"",""6765""]],
PRIMEM[""Greenwich"",0,
AUTHORITY[""EPSG"",""8901""]],
UNIT[""degree"",0.01745329251994328,
AUTHORITY[""EPSG"",""9122""]],
AUTHORITY[""EPSG"",""4765""]],
UNIT[""metre"",1,
AUTHORITY[""EPSG"",""9001""]],
PROJECTION[""Transverse_Mercator""],
PARAMETER[""latitude_of_origin"",0],
PARAMETER[""central_meridian"",15],
PARAMETER[""scale_factor"",0.9999],
PARAMETER[""false_easting"",500000],
PARAMETER[""false_northing"",-5000000],
AUTHORITY[""EPSG"",""3794""],
AXIS[""Easting"",EAST],
AXIS[""Northing"",NORTH]]"), GeographicCoordinateSystem.WGS84).MathTransform;

public bool Done { get; } = false;
public bool GeometryChanged { get; } = true;

public void Filter(CoordinateSequence seq, int i)
{
var (x, y) = _mathTransform.Transform(seq.GetX(i), seq.GetY(i));
seq.SetX(i, x);
seq.SetY(i, y);
}
}
}
290 changes: 290 additions & 0 deletions OsmGursBuildingImport/GursData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading.Tasks;
using NetTopologySuite;
using NetTopologySuite.Algorithm;
using NetTopologySuite.Features;
using NetTopologySuite.Geometries;
using NetTopologySuite.Geometries.Utilities;
using NetTopologySuite.Index.Strtree;
using NetTopologySuite.IO;
using Newtonsoft.Json;

namespace OsmGursBuildingImport
{
record BilingualName(string Name, string NameSecondLanguage);
record PostInfo(short Id, BilingualName Name);
record VotingArea(Geometry Geometry, string Name, string Id);
record BuildingInfo(long Id, Geometry Geometry, string? Date, List<Address>? Addresses, int? ConstructionYear);
record Address(long Id, Geometry Geometry, string Date, string HouseNumber, BilingualName StreetName, PostInfo PostInfo, BilingualName VillageName);
record ProcessingArea(Geometry Geometry, string Name, List<BuildingInfo> Buildings, string pathToPoly)
{
public bool Process { get; set; }
}

class GursData
{
private static GeometryFactory D96Factory = NtsGeometryServices.Instance.CreateGeometryFactory(new PrecisionModel(), 3794);
public Dictionary<string, ProcessingArea> ProcessingAreas = new();

Dictionary<long, Address> Addresses = new();
List<VotingArea> VotingAreas = new();
Dictionary<string, Dictionary<string, string>> Overrides = new();
STRtree<BuildingInfo> BuildingsIndex = new();
Dictionary<long, List<Address>> BuildingToAddresses = new();

public GursData(string dir, string overridesDir, string tempDir)
{
LoadOverrides(overridesDir);
LoadAddresses(dir);
LoadBuildings(dir);
LoadVotingAreasGeoJson();

BuildProcessingAreas(tempDir);
}

private static string WritePoly(string poliesDir, Geometry geometry, string id)
{
List<Polygon> polygons;
if (geometry is MultiPolygon mp)
{
polygons = mp.Geometries.OfType<Polygon>().ToList();
if (polygons.Count != mp.Geometries.Length)
throw new Exception(string.Join(", ", mp.Geometries.Select(g => g.GetType().ToString())));
}
else if (geometry is Polygon poly2)
{
polygons = new() { poly2 };
}
else
{
throw new Exception(geometry.GetType().ToString());
}

var fixedPolygons = new List<Polygon>();
foreach (var poly in polygons)
{
var fixedPoly = GeometryFixer.Fix(poly);
if (fixedPoly is Polygon poly2)
{
fixedPolygons.Add(poly2);
}
else if (fixedPoly is MultiPolygon multiPolygon)
{
foreach (var poly3 in multiPolygon.Geometries.OfType<Polygon>())
{
fixedPolygons.Add(poly3);
}
}
else
{
throw new NotImplementedException(fixedPoly.GetType().ToString());
}
}

string polyPath = Path.Combine(poliesDir, id + ".poly");
using var sw = new StreamWriter(polyPath);
sw.WriteLine(id + ".original");
for (int i = 0; i < fixedPolygons.Count; i++)
{
sw.WriteLine("poly" + (i + 1));
foreach (var cord in fixedPolygons[i].ExteriorRing.Coordinates)
{
sw.WriteLine($"\t{cord.X} {cord.Y}");
}
sw.WriteLine("END");
}
sw.WriteLine("END");
return polyPath;
}


private void BuildProcessingAreas(string tempDir)
{
var poliesDir = Path.Combine(tempDir, "polygons");
Directory.CreateDirectory(poliesDir);

foreach (var votingArea in VotingAreas)
{
var newArea = new ProcessingArea(
votingArea.Geometry,
votingArea.Id,
new List<BuildingInfo>(),
WritePoly(poliesDir, votingArea.Geometry, votingArea.Id));
ProcessingAreas.Add(votingArea.Id, newArea);
}

Parallel.ForEach(ProcessingAreas.Values, (area) => {
foreach (var aprox in BuildingsIndex.Query(area.Geometry.EnvelopeInternal))
{
if (!area.Geometry.Intersects(aprox.Geometry))
continue;
area.Buildings.Add(aprox);
}
});
}

private void LoadOverrides(string overridesDir)
{
foreach (var file in Directory.GetFiles(overridesDir))
{
using var csv = Sylvan.Data.Csv.CsvDataReader.Create(file);
var dict = Overrides[Path.GetFileNameWithoutExtension(file)] = new();
while (csv.Read())
{
dict.Add(csv.GetString(0), csv.GetString(1));
}
}
}

private static string OverrideString(Dictionary<string, string> dict, string original)
{
if (dict.TryGetValue(original, out var overriden))
return overriden;
return original;
}

void LoadAddresses(string dir)
{
var streetNameOverride = Overrides["UL_UIME"];
var streetNameSecondaryLanguageOverride = Overrides["UL_DJ"];
var settlementNameOverride = Overrides["NA_UIME"];
var postalNameOverride = Overrides["PT_UIME"];

using var csvAddresses = Sylvan.Data.Csv.CsvDataReader.Create(Directory.GetFiles(Path.Combine(dir, "Addresses"), "KN_SLO_NASLOVI_HS_*.csv").Single());
var wktReader = new WKTReader(D96Factory.GeometryServices);
while (csvAddresses.Read())
{
var buildingId = ConvertToOldGursBuildingId(csvAddresses.GetInt64("EID_STAVBA"));
var id = ConvertToOldGursAddressId(csvAddresses.GetInt64("EID_HISNA_STEVILKA"));
var geom = new Point(csvAddresses.GetDouble("E"), csvAddresses.GetDouble("N"));
geom.Apply(D96Converter.Instance);//Convert from D96 to OSM coordinate system
var houseNumber = csvAddresses.GetString("HS_STEVILKA") + csvAddresses.GetString("HS_DODATEK");
var streetName = new BilingualName(OverrideString(streetNameOverride, csvAddresses.GetString("ULICA_NAZIV")), OverrideString(streetNameSecondaryLanguageOverride, csvAddresses.GetString("ULICA_NAZIV_DJ")));
var postInfo = new PostInfo(csvAddresses.GetInt16("POSTNI_OKOLIS_SIFRA"), new BilingualName(OverrideString(postalNameOverride, csvAddresses.GetString("POSTNI_OKOLIS_NAZIV")), csvAddresses.GetString("POSTNI_OKOLIS_NAZIV_DJ")));
var villageName = new BilingualName(OverrideString(settlementNameOverride, csvAddresses.GetString("NASELJE_NAZIV")), csvAddresses.GetString("NASELJE_NAZIV_DJ"));
if (streetName.Name == "")
streetName = villageName;
var address = new Address(id, geom, null, houseNumber, streetName, postInfo, villageName);
Addresses.Add(id, address);
if (BuildingToAddresses.TryGetValue(buildingId, out var list))
list.Add(address);
else
BuildingToAddresses.Add(buildingId, new List<Address>() { address });
}
}

static long ConvertToOldGursBuildingId(long newId)
{
newId /= 10;//Remove control value
var result = newId % 1000_000_000_000;
if (result + 10020000000000000 != newId)
throw new Exception("Failed converting new GURS building EID to old sta_sid");
return result;
}

static long ConvertToOldGursAddressId(long newId)
{
newId /= 10;//Remove control value
var result = newId % 1000_000_000_000;
if (result + 10040000000000000 != newId)
throw new Exception("Failed converting new GURS building EID to old sta_sid");
return result;
}

void LoadVotingAreasGeoJson()
{
using var sr = new StreamReader("VLV.geojson");
var reader = new GeoJsonReader();
var features = reader.Read<FeatureCollection>(sr.ReadToEnd());
var duplicatedVotingAreas = new List<VotingArea>();
foreach (var feature in features)
{
if (feature.Attributes["ENOTA"]?.ToString() != "LV")
continue;
var id = feature.Attributes["VLV_ID"].ToString();
var name = feature.Attributes["VLV_UIME"].ToString();
var geometry = feature.Geometry;
duplicatedVotingAreas.Add(new VotingArea(geometry, name, id));
}

foreach (var groupedById in duplicatedVotingAreas.GroupBy(v => v.Id))
{
if (groupedById.Count() == 1)
{
VotingAreas.Add(groupedById.Single());
continue;
}
int index = 0;
foreach (var area in groupedById.OrderByDescending(g => g.Geometry.Area))
{
VotingAreas.Add(new(area.Geometry, area.Name + " #" + index, area.Id + "_" + index));
index++;
}
}
}

void LoadBuildings(string dir)
{
var buildingsPolygons = new Dictionary<long, Geometry>();
var shapeReader = new ShapefileDataReader(Path.Combine(dir, "Buildings", "KN_SLO_STAVBE_SLO_STAVBE_NADZEMNI_TLORIS", "KN_SLO_STAVBE_SLO_STAVBE_NADZEMNI_TLORIS_poligon.shp"), D96Factory);
while (shapeReader.Read())
{
shapeReader.Geometry.Apply(D96Converter.Instance);
var id = ConvertToOldGursBuildingId(shapeReader.GetInt64(2));
buildingsPolygons.Add(id, shapeReader.Geometry);
}

shapeReader = new ShapefileDataReader(Path.Combine(dir, "Buildings", "KN_SLO_STAVBE_SLO_STAVBE_TLORIS", "KN_SLO_STAVBE_SLO_STAVBE_TLORIS_poligon.shp"), D96Factory);
while (shapeReader.Read())
{
shapeReader.Geometry.Apply(D96Converter.Instance);
var id = ConvertToOldGursBuildingId(shapeReader.GetInt64(2));
if (buildingsPolygons.ContainsKey(id))
continue;
buildingsPolygons.Add(id, shapeReader.Geometry);
}
shapeReader = new ShapefileDataReader(Path.Combine(dir, "Buildings", "KN_SLO_STAVBE_SLO_STAVBE", "KN_SLO_STAVBE_SLO_STAVBE_tocka.shp"), D96Factory);
while (shapeReader.Read())
{
var id = ConvertToOldGursBuildingId(shapeReader.GetInt64(2));

if (!buildingsPolygons.TryGetValue(id, out var geometry))
{
shapeReader.Geometry.Apply(D96Converter.Instance);
//Console.WriteLine($"Building with id {id} at {shapeReader.Geometry}, does not have tloris polygon!");
continue;
}

if (!BuildingToAddresses.TryGetValue(id, out var addresses))
addresses = null;

var yearOfConstruction = shapeReader["LETO_IZGRA"] switch {
double val => (int)val,
_ => (int?)null
};

if (yearOfConstruction > 2050 || yearOfConstruction < 1000)
{
Console.WriteLine($"Year of construction outside range. {yearOfConstruction}");
yearOfConstruction = null;
}
BuildingsIndex.Insert(geometry.EnvelopeInternal, new BuildingInfo(
id,
geometry,
null,
addresses,
yearOfConstruction
));
}
BuildingsIndex.Build();
}
}
}
Loading