Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #if UNITY_EDITOR
- /*
- Usage: Run the wizard under Tools/CityGen3D/Import Bing Buildings in the menu, point the
- json filename string to your .geojson file path, and click Create.
- Note: this assumes you have already loaded and processed a CityGen3D map for your desired area.
- If you don't want to clash with any of the sparse buildings from OSM, you may want to delete those
- first from inside MapBuildings, or move them outside to another empty game object.
- Also make sure Newtonsoft JSON is installed in your Unity packages, and install it if it isn't
- (i.e. add from GIT URL of com.unity.nuget.newtonsoft-json).
- New map buildings will be created and named with the "Bing_" prefix by default, and you can
- untick the checkbox to disable automatically deleting these if rerunning the wizard.
- Bing building footprints for most countries can be found (and unzipped into .geojson forms) from one of the repositories
- under: https://github.com/microsoft?q=building+footprints&type=all&language=&sort=
- Some notable exceptions such as Japan and South Korea are currently absent, but Microsoft may add these in the future.
- Note that Bing footprints do not contain any building type or height information, and have been derived via Microsoft's
- automated machine learning which is not perfect at detecting building shapes, so this data is mostly useful only in
- areas where OSM data is very sparse and where the caveat of poorer quality and no tagging is to be accepted as a compromise.
- All buildings will be created as single level houses, which can however be changed manually afterwards under the MapBuildings
- parent to better reflect the actual building in question.
- */
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.IO;
- using System.Text;
- using CityGen3D;
- using Newtonsoft.Json;
- using Unity.EditorCoroutines.Editor;
- using UnityEditor;
- using UnityEngine;
- // ReSharper disable ArrangeObjectCreationWhenTypeEvident
- namespace Tesseraction.CityGenExtensions.Bing
- {
- public class ImportBingBuildingsWizard : ScriptableWizard
- {
- // Change this to point to the path of your .geojson file, either here in the script, or at runtime in the wizard
- public string jsonFilename = @"E:\development_extras\melbourne\Bing Footprints\Australia.geojson";
- // Change this to attach a prefix to the created houses, to make discovery/deletion easier
- public string buildingNamePrefix = "Bing_";
- // How many features to process per batch - smaller numbers equals more rapid progress bar updates
- public int featureBatchSize = 100000;
- // Use this to remove and replace buildings from any previous wizard runs, by detecting the prefix from above
- public bool deletePreviousImports = true;
- private GeoCoord _origin;
- private MapCoord _minMapPos;
- private MapCoord _maxMapPos;
- [MenuItem ("Tools/CityGen3D/Import Bing Buildings", priority = 51)]
- private static void CreateWizard ()
- {
- DisplayWizard ("Import Bing Buildings", typeof (ImportBingBuildingsWizard), "Import Bing Buildings");
- }
- private void OnWizardCreate ()
- {
- EditorCoroutineUtility.StartCoroutine (RunWizard (), this);
- }
- private IEnumerator RunWizard ()
- {
- EditorUtility.DisplayProgressBar ("Cleaning up", "Clearing previous imports", 0.05f);
- yield return null;
- if (deletePreviousImports)
- {
- for (int i = Map.Instance.mapBuildings.transform.childCount - 1; i >= 0; i--)
- {
- GameObject child = Map.Instance.mapBuildings.transform.GetChild (i).gameObject;
- if (child.name.StartsWith (buildingNamePrefix))
- {
- DestroyImmediate (child);
- }
- }
- }
- _origin = Map.Instance.data.GetOrigin ();
- _minMapPos = Map.Instance.data.minLocation.GetMapCoord (_origin);
- _maxMapPos = Map.Instance.data.maxLocation.GetMapCoord (_origin);
- yield return ProcessFootprints (jsonFilename);
- Debug.Log ("Finished.");
- EditorUtility.ClearProgressBar ();
- }
- private IEnumerator ProcessFootprints (string filename)
- {
- FileInfo fileInfo = new FileInfo (filename);
- long fileSize = fileInfo.Length;
- EditorUtility.DisplayProgressBar ("Read GeoJSON data", "Processing batch 1", 0.1f);
- yield return null;
- int lineCount = 0;
- long charCount = 0L;
- int featureCount = 0;
- int batchCount = 0;
- List<string> jsonLines = new List<string> ();
- using (StreamReader reader = new StreamReader (filename))
- {
- while (reader.ReadLine () is { } jsonLine)
- {
- string trimmedLine = jsonLine.Trim ();
- lineCount++;
- charCount += jsonLine.Length;
- // Skip first line, collection array markers, and collection tag
- // ReSharper disable once ConvertIfStatementToSwitchStatement
- if ((trimmedLine == "{" && lineCount == 1) ||
- trimmedLine == "\"type\": \"FeatureCollection\"," ||
- trimmedLine == "\"features\": [")
- {
- continue;
- }
- if (trimmedLine.Contains ("\"type\": \"Feature\","))
- {
- featureCount++;
- if (featureCount >= featureBatchSize)
- {
- batchCount++;
- if (trimmedLine.StartsWith ("{") && trimmedLine.EndsWith ("}"))
- {
- // The big repo of ML country data sets appears to use this variation
- ProcessBatch (jsonLines, batchCount, true);
- jsonLines.Clear ();
- }
- else
- {
- // The per-country repos seem to use this format
- jsonLines.RemoveAt (jsonLines.Count - 1);
- jsonLines.RemoveAt (jsonLines.Count - 1);
- jsonLines.Add ("}");
- ProcessBatch (jsonLines, batchCount, false);
- jsonLines.Clear ();
- jsonLines.Add ("{");
- }
- jsonLines.Add (trimmedLine);
- double approximateBatchSize = charCount / (double) batchCount;
- double approximateNumberBatches = fileSize / approximateBatchSize;
- double progress = 0.1d + batchCount / approximateNumberBatches * 0.9d;
- if (EditorUtility.DisplayCancelableProgressBar ("Processing GeoJSON data",
- "Processing batch " + batchCount.ToString ("N0") + " of an estimated " +
- ((int) approximateNumberBatches).ToString ("N0"), (float) progress))
- {
- EditorUtility.ClearProgressBar ();
- break;
- }
- yield return null;
- featureCount = 0;
- }
- }
- jsonLines.Add (trimmedLine);
- if (!reader.EndOfStream)
- {
- continue;
- }
- if (trimmedLine.StartsWith ("{") && trimmedLine.EndsWith ("}"))
- {
- ProcessBatch (jsonLines, batchCount, true);
- }
- else
- {
- // Final batch needs to sack one extra line for the closing bracket
- jsonLines.RemoveAt (jsonLines.Count - 1);
- jsonLines.RemoveAt (jsonLines.Count - 1);
- jsonLines.RemoveAt (jsonLines.Count - 1);
- jsonLines.Add ("}");
- ProcessBatch (jsonLines, batchCount, false);
- }
- }
- }
- }
- private void ProcessBatch (List<string> jsonLines, int batchNumber, bool hasSelfContainedLines)
- {
- StringBuilder sb = new StringBuilder ();
- sb.Clear ();
- foreach (string line in jsonLines)
- {
- sb.Append (line);
- if (hasSelfContainedLines)
- {
- sb.Append (",");
- }
- }
- string json = sb.ToString ().TrimEnd (',');
- string embedded = "{\"type\": \"FeatureCollection\", \"features\": [" + json + "]}";
- Root jsonObject = JsonConvert.DeserializeObject<Root> (embedded);
- if (jsonObject == null)
- {
- throw new ArgumentNullException ();
- }
- int footprintsInRange = 0;
- List<Vector3> buildingPoints = new List<Vector3> ();
- foreach (Feature feature in jsonObject.features)
- {
- foreach (List<List<double>> coords in feature.geometry.coordinates)
- {
- buildingPoints.Clear ();
- bool isInRange = false;
- foreach (List<double> doubles in coords)
- {
- if (doubles.Count != 2)
- {
- throw new ArgumentOutOfRangeException ();
- }
- GeoCoord geoCoord = new GeoCoord (doubles [1], doubles [0]);
- MapCoord mapPos = geoCoord.GetMapCoord (_origin);
- Vector3 worldPos = mapPos.GetPosition ();
- buildingPoints.Add (worldPos);
- // ReSharper disable once InvertIf
- if (mapPos.x >= _minMapPos.x && mapPos.y >= _minMapPos.y &&
- mapPos.x <= _maxMapPos.x && mapPos.y <= _maxMapPos.y)
- {
- footprintsInRange++;
- isInRange = true;
- }
- }
- if (!isInRange)
- {
- continue;
- }
- GameObject building = Map.Instance.mapBuildings.Create (0, buildingPoints);
- building.name = buildingNamePrefix + building.name;
- // ReSharper disable once InvertIf
- if (building != null)
- {
- building.GetComponent<MapBuilding> ().RefreshPivot ();
- building.GetComponent<MapBuilding> ().BuildMap ();
- }
- }
- }
- if (footprintsInRange > 0)
- {
- Debug.Log ("Batch # " + batchNumber.ToString ("N0") +
- " -- found and constructed " + footprintsInRange.ToString ("N0") + " building footprints");
- }
- }
- // ReSharper disable InconsistentNaming
- // ReSharper disable ClassNeverInstantiated.Local
- // ReSharper disable UnusedAutoPropertyAccessor.Local
- // ReSharper disable UnusedMember.Local
- // ReSharper disable CollectionNeverUpdated.Local
- private class Feature
- {
- public string type { get; set; }
- public Geometry geometry { get; set; }
- public Properties properties { get; set; }
- }
- private class Geometry
- {
- public string type { get; set; }
- public List<List<List<double>>> coordinates { get; set; }
- }
- private class Properties
- {
- }
- private class Root
- {
- public string type { get; set; }
- public List<Feature> features { get; set; }
- }
- // ReSharper restore CollectionNeverUpdated.Local
- // ReSharper restore UnusedMember.Local
- // ReSharper restore UnusedAutoPropertyAccessor.Local
- // ReSharper restore ClassNeverInstantiated.Local
- // ReSharper restore InconsistentNaming
- }
- }
- #endif
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement