Advertisement
chmodseven

Import Bing Building Footprints

Aug 27th, 2022 (edited)
993
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 12.65 KB | None | 0 0
  1. #if UNITY_EDITOR
  2.  
  3. /*
  4.  
  5. Usage: Run the wizard under Tools/CityGen3D/Import Bing Buildings in the menu, point the
  6. json filename string to your .geojson file path, and click Create.
  7.  
  8. Note: this assumes you have already loaded and processed a CityGen3D map for your desired area.
  9. If you don't want to clash with any of the sparse buildings from OSM, you may want to delete those
  10. first from inside MapBuildings, or move them outside to another empty game object.
  11. Also make sure Newtonsoft JSON is installed in your Unity packages, and install it if it isn't
  12. (i.e. add from GIT URL of com.unity.nuget.newtonsoft-json).
  13. New map buildings will be created and named with the "Bing_" prefix by default, and you can
  14. untick the checkbox to disable automatically deleting these if rerunning the wizard.
  15.  
  16. Bing building footprints for most countries can be found (and unzipped into .geojson forms) from one of the repositories
  17. under: https://github.com/microsoft?q=building+footprints&type=all&language=&sort=
  18. Some notable exceptions such as Japan and South Korea are currently absent, but Microsoft may add these in the future.
  19. Note that Bing footprints do not contain any building type or height information, and have been derived via Microsoft's
  20. automated machine learning which is not perfect at detecting building shapes, so this data is mostly useful only in
  21. areas where OSM data is very sparse and where the caveat of poorer quality and no tagging is to be accepted as a compromise.
  22. All buildings will be created as single level houses, which can however be changed manually afterwards under the MapBuildings
  23. parent to better reflect the actual building in question.
  24.  
  25. */
  26.  
  27. using System;
  28. using System.Collections;
  29. using System.Collections.Generic;
  30. using System.IO;
  31. using System.Text;
  32. using CityGen3D;
  33. using Newtonsoft.Json;
  34. using Unity.EditorCoroutines.Editor;
  35. using UnityEditor;
  36. using UnityEngine;
  37.  
  38. // ReSharper disable ArrangeObjectCreationWhenTypeEvident
  39.  
  40. namespace Tesseraction.CityGenExtensions.Bing
  41. {
  42.     public class ImportBingBuildingsWizard : ScriptableWizard
  43.     {
  44.         // Change this to point to the path of your .geojson file, either here in the script, or at runtime in the wizard
  45.         public string jsonFilename = @"E:\development_extras\melbourne\Bing Footprints\Australia.geojson";
  46.  
  47.         // Change this to attach a prefix to the created houses, to make discovery/deletion easier
  48.         public string buildingNamePrefix = "Bing_";
  49.  
  50.         // How many features to process per batch - smaller numbers equals more rapid progress bar updates
  51.         public int featureBatchSize = 100000;
  52.  
  53.         // Use this to remove and replace buildings from any previous wizard runs, by detecting the prefix from above
  54.         public bool deletePreviousImports = true;
  55.  
  56.         private GeoCoord _origin;
  57.         private MapCoord _minMapPos;
  58.         private MapCoord _maxMapPos;
  59.  
  60.         [MenuItem ("Tools/CityGen3D/Import Bing Buildings", priority = 51)]
  61.         private static void CreateWizard ()
  62.         {
  63.             DisplayWizard ("Import Bing Buildings", typeof (ImportBingBuildingsWizard), "Import Bing Buildings");
  64.         }
  65.  
  66.         private void OnWizardCreate ()
  67.         {
  68.             EditorCoroutineUtility.StartCoroutine (RunWizard (), this);
  69.         }
  70.  
  71.         private IEnumerator RunWizard ()
  72.         {
  73.             EditorUtility.DisplayProgressBar ("Cleaning up", "Clearing previous imports", 0.05f);
  74.             yield return null;
  75.  
  76.             if (deletePreviousImports)
  77.             {
  78.                 for (int i = Map.Instance.mapBuildings.transform.childCount - 1; i >= 0; i--)
  79.                 {
  80.                     GameObject child = Map.Instance.mapBuildings.transform.GetChild (i).gameObject;
  81.                     if (child.name.StartsWith (buildingNamePrefix))
  82.                     {
  83.                         DestroyImmediate (child);
  84.                     }
  85.                 }
  86.             }
  87.  
  88.             _origin = Map.Instance.data.GetOrigin ();
  89.             _minMapPos = Map.Instance.data.minLocation.GetMapCoord (_origin);
  90.             _maxMapPos = Map.Instance.data.maxLocation.GetMapCoord (_origin);
  91.  
  92.             yield return ProcessFootprints (jsonFilename);
  93.             Debug.Log ("Finished.");
  94.             EditorUtility.ClearProgressBar ();
  95.         }
  96.  
  97.         private IEnumerator ProcessFootprints (string filename)
  98.         {
  99.             FileInfo fileInfo = new FileInfo (filename);
  100.             long fileSize = fileInfo.Length;
  101.  
  102.             EditorUtility.DisplayProgressBar ("Read GeoJSON data", "Processing batch 1", 0.1f);
  103.             yield return null;
  104.  
  105.             int lineCount = 0;
  106.             long charCount = 0L;
  107.             int featureCount = 0;
  108.             int batchCount = 0;
  109.             List<string> jsonLines = new List<string> ();
  110.  
  111.             using (StreamReader reader = new StreamReader (filename))
  112.             {
  113.                 while (reader.ReadLine () is { } jsonLine)
  114.                 {
  115.                     string trimmedLine = jsonLine.Trim ();
  116.                     lineCount++;
  117.                     charCount += jsonLine.Length;
  118.  
  119.                     // Skip first line, collection array markers, and collection tag
  120.                     // ReSharper disable once ConvertIfStatementToSwitchStatement
  121.                     if ((trimmedLine == "{" && lineCount == 1) ||
  122.                         trimmedLine == "\"type\": \"FeatureCollection\"," ||
  123.                         trimmedLine == "\"features\": [")
  124.                     {
  125.                         continue;
  126.                     }
  127.  
  128.                     if (trimmedLine.Contains ("\"type\": \"Feature\","))
  129.                     {
  130.                         featureCount++;
  131.                         if (featureCount >= featureBatchSize)
  132.                         {
  133.                             batchCount++;
  134.                             if (trimmedLine.StartsWith ("{") && trimmedLine.EndsWith ("}"))
  135.                             {
  136.                                 // The big repo of ML country data sets appears to use this variation
  137.                                 ProcessBatch (jsonLines, batchCount, true);
  138.                                 jsonLines.Clear ();
  139.                             }
  140.                             else
  141.                             {
  142.                                 // The per-country repos seem to use this format
  143.                                 jsonLines.RemoveAt (jsonLines.Count - 1);
  144.                                 jsonLines.RemoveAt (jsonLines.Count - 1);
  145.                                 jsonLines.Add ("}");
  146.                                 ProcessBatch (jsonLines, batchCount, false);
  147.                                 jsonLines.Clear ();
  148.                                 jsonLines.Add ("{");
  149.                             }
  150.                             jsonLines.Add (trimmedLine);
  151.  
  152.                             double approximateBatchSize = charCount / (double) batchCount;
  153.                             double approximateNumberBatches = fileSize / approximateBatchSize;
  154.                             double progress = 0.1d + batchCount / approximateNumberBatches * 0.9d;
  155.                             if (EditorUtility.DisplayCancelableProgressBar ("Processing GeoJSON data",
  156.                                     "Processing batch " + batchCount.ToString ("N0") + " of an estimated " +
  157.                                     ((int) approximateNumberBatches).ToString ("N0"), (float) progress))
  158.                             {
  159.                                 EditorUtility.ClearProgressBar ();
  160.                                 break;
  161.                             }
  162.                             yield return null;
  163.                             featureCount = 0;
  164.                         }
  165.                     }
  166.  
  167.                     jsonLines.Add (trimmedLine);
  168.                     if (!reader.EndOfStream)
  169.                     {
  170.                         continue;
  171.                     }
  172.  
  173.                     if (trimmedLine.StartsWith ("{") && trimmedLine.EndsWith ("}"))
  174.                     {
  175.                         ProcessBatch (jsonLines, batchCount, true);
  176.                     }
  177.                     else
  178.                     {
  179.                         // Final batch needs to sack one extra line for the closing bracket
  180.                         jsonLines.RemoveAt (jsonLines.Count - 1);
  181.                         jsonLines.RemoveAt (jsonLines.Count - 1);
  182.                         jsonLines.RemoveAt (jsonLines.Count - 1);
  183.                         jsonLines.Add ("}");
  184.                         ProcessBatch (jsonLines, batchCount, false);
  185.                     }
  186.                 }
  187.             }
  188.         }
  189.  
  190.         private void ProcessBatch (List<string> jsonLines, int batchNumber, bool hasSelfContainedLines)
  191.         {
  192.             StringBuilder sb = new StringBuilder ();
  193.             sb.Clear ();
  194.             foreach (string line in jsonLines)
  195.             {
  196.                 sb.Append (line);
  197.                 if (hasSelfContainedLines)
  198.                 {
  199.                     sb.Append (",");
  200.                 }
  201.             }
  202.             string json = sb.ToString ().TrimEnd (',');
  203.             string embedded = "{\"type\": \"FeatureCollection\", \"features\": [" + json + "]}";
  204.  
  205.             Root jsonObject = JsonConvert.DeserializeObject<Root> (embedded);
  206.             if (jsonObject == null)
  207.             {
  208.                 throw new ArgumentNullException ();
  209.             }
  210.  
  211.             int footprintsInRange = 0;
  212.             List<Vector3> buildingPoints = new List<Vector3> ();
  213.             foreach (Feature feature in jsonObject.features)
  214.             {
  215.                 foreach (List<List<double>> coords in feature.geometry.coordinates)
  216.                 {
  217.                     buildingPoints.Clear ();
  218.                     bool isInRange = false;
  219.                     foreach (List<double> doubles in coords)
  220.                     {
  221.                         if (doubles.Count != 2)
  222.                         {
  223.                             throw new ArgumentOutOfRangeException ();
  224.                         }
  225.                         GeoCoord geoCoord = new GeoCoord (doubles [1], doubles [0]);
  226.                         MapCoord mapPos = geoCoord.GetMapCoord (_origin);
  227.                         Vector3 worldPos = mapPos.GetPosition ();
  228.                         buildingPoints.Add (worldPos);
  229.  
  230.                         // ReSharper disable once InvertIf
  231.                         if (mapPos.x >= _minMapPos.x && mapPos.y >= _minMapPos.y &&
  232.                             mapPos.x <= _maxMapPos.x && mapPos.y <= _maxMapPos.y)
  233.                         {
  234.                             footprintsInRange++;
  235.                             isInRange = true;
  236.                         }
  237.                     }
  238.  
  239.                     if (!isInRange)
  240.                     {
  241.                         continue;
  242.                     }
  243.  
  244.                     GameObject building = Map.Instance.mapBuildings.Create (0, buildingPoints);
  245.                     building.name = buildingNamePrefix + building.name;
  246.  
  247.                     // ReSharper disable once InvertIf
  248.                     if (building != null)
  249.                     {
  250.                         building.GetComponent<MapBuilding> ().RefreshPivot ();
  251.                         building.GetComponent<MapBuilding> ().BuildMap ();
  252.                     }
  253.                 }
  254.             }
  255.  
  256.             if (footprintsInRange > 0)
  257.             {
  258.                 Debug.Log ("Batch # " + batchNumber.ToString ("N0") +
  259.                            " -- found and constructed " + footprintsInRange.ToString ("N0") + " building footprints");
  260.             }
  261.         }
  262.  
  263.         // ReSharper disable InconsistentNaming
  264.         // ReSharper disable ClassNeverInstantiated.Local
  265.         // ReSharper disable UnusedAutoPropertyAccessor.Local
  266.         // ReSharper disable UnusedMember.Local
  267.         // ReSharper disable CollectionNeverUpdated.Local
  268.         private class Feature
  269.         {
  270.             public string type { get; set; }
  271.             public Geometry geometry { get; set; }
  272.             public Properties properties { get; set; }
  273.         }
  274.  
  275.         private class Geometry
  276.         {
  277.             public string type { get; set; }
  278.             public List<List<List<double>>> coordinates { get; set; }
  279.         }
  280.  
  281.         private class Properties
  282.         {
  283.         }
  284.  
  285.         private class Root
  286.         {
  287.             public string type { get; set; }
  288.             public List<Feature> features { get; set; }
  289.         }
  290.         // ReSharper restore CollectionNeverUpdated.Local
  291.         // ReSharper restore UnusedMember.Local
  292.         // ReSharper restore UnusedAutoPropertyAccessor.Local
  293.         // ReSharper restore ClassNeverInstantiated.Local
  294.         // ReSharper restore InconsistentNaming
  295.     }
  296. }
  297.  
  298. #endif
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement