From 451c8570ad210aaec75b9240cbe1c30e8a0cd7a4 Mon Sep 17 00:00:00 2001
From: Adrian Lara <adrian@devetry.com>
Date: Tue, 13 Oct 2020 11:05:28 -0400
Subject: [PATCH 1/3] Add property upload GeoJSON button

---
 src/app/app.module.ts                         |  4 +-
 src/app/home/home.component.html              |  2 +
 src/app/home/home.component.scss              |  2 +-
 .../properties-upload.component.html          |  8 ++++
 .../properties-upload.component.scss          |  0
 .../properties-upload.component.spec.ts       | 25 +++++++++++
 .../properties-upload.component.ts            | 42 +++++++++++++++++++
 7 files changed, 81 insertions(+), 2 deletions(-)
 create mode 100644 src/app/properties-upload/properties-upload.component.html
 create mode 100644 src/app/properties-upload/properties-upload.component.scss
 create mode 100644 src/app/properties-upload/properties-upload.component.spec.ts
 create mode 100644 src/app/properties-upload/properties-upload.component.ts

diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 5e99006..66a79a4 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -20,6 +20,7 @@ import { MaterialModule } from './material-module';
 import { SharedModule } from './shared/shared.module';
 import { MapComponent } from './map/map.component';
 import { InventoryComponent } from './inventory/inventory.component';
+import { PropertiesUploadComponent } from './properties-upload/properties-upload.component';
 
 // AoT requires an exported function for factories
 export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
@@ -33,7 +34,8 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
     HeaderComponent,
     LeftNavComponent,
     MapComponent,
-    InventoryComponent
+    InventoryComponent,
+    PropertiesUploadComponent
   ],
   imports: [
     AppRoutingModule,
diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html
index f582814..83a27b1 100644
--- a/src/app/home/home.component.html
+++ b/src/app/home/home.component.html
@@ -15,4 +15,6 @@ <h1 class="title">Running: {{ status.running }}</h1>
   </div>
 
   <textarea disabled>{{ latestQueryResult }}</textarea>
+  
+  <app-properties-upload></app-properties-upload>
 </div>
diff --git a/src/app/home/home.component.scss b/src/app/home/home.component.scss
index 99a01f7..925d112 100644
--- a/src/app/home/home.component.scss
+++ b/src/app/home/home.component.scss
@@ -10,5 +10,5 @@
 
 textarea {
   width: 600px;
-  height: 400px;
+  height: 200px;
 }
diff --git a/src/app/properties-upload/properties-upload.component.html b/src/app/properties-upload/properties-upload.component.html
new file mode 100644
index 0000000..79e6b22
--- /dev/null
+++ b/src/app/properties-upload/properties-upload.component.html
@@ -0,0 +1,8 @@
+<div>
+    <label for="file">Choose building GeoJSON file</label>
+    <input 
+        type="file"
+        id="file"
+        accept=".geojson"
+        (change)="handleFileInput($event.target.files)">
+</div>
diff --git a/src/app/properties-upload/properties-upload.component.scss b/src/app/properties-upload/properties-upload.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/properties-upload/properties-upload.component.spec.ts b/src/app/properties-upload/properties-upload.component.spec.ts
new file mode 100644
index 0000000..b8144a6
--- /dev/null
+++ b/src/app/properties-upload/properties-upload.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PropertiesUploadComponent } from './properties-upload.component';
+
+describe('PropertiesUploadComponent', () => {
+  let component: PropertiesUploadComponent;
+  let fixture: ComponentFixture<PropertiesUploadComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ PropertiesUploadComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(PropertiesUploadComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/app/properties-upload/properties-upload.component.ts b/src/app/properties-upload/properties-upload.component.ts
new file mode 100644
index 0000000..d0fd403
--- /dev/null
+++ b/src/app/properties-upload/properties-upload.component.ts
@@ -0,0 +1,42 @@
+import { Component, OnInit } from '@angular/core';
+import { PropertyService } from '../core/services/property/property.service';
+
+
+@Component({
+  selector: 'app-properties-upload',
+  templateUrl: './properties-upload.component.html',
+  styleUrls: ['./properties-upload.component.scss']
+})
+export class PropertiesUploadComponent implements OnInit {
+  constructor(private propertyService: PropertyService) {}
+
+  ngOnInit(): void {
+  }
+
+  handleFileInput(files: FileList): void {
+    if (files[0]) {
+      files[0].text().then(text => {
+        const geojson = JSON.parse(text);
+        const crs = geojson.crs ? geojson.crs : { type: 'name', properties: { name: 'EPSG:4326'} };
+
+        geojson.features.forEach(feature => {
+          const extra_data = feature.properties;
+          feature.geometry.crs = crs;
+          
+          // extract non-extra_data attributes
+          const ubid = extra_data.UBID;
+          delete extra_data.UBID;
+          
+          this.propertyService.model.create({
+            extra_data: extra_data,
+            footprint: feature.geometry,
+            ubid: ubid
+          }).catch((err) => { 
+            console.error(err);
+          });
+        });
+      })
+    }
+  }
+
+}

From 84bd0d3e4f8828d875c244d764493db899c69917 Mon Sep 17 00:00:00 2001
From: Adrian Lara <adrian@devetry.com>
Date: Tue, 13 Oct 2020 11:18:28 -0400
Subject: [PATCH 2/3] Add tax lot upload GeoJSON button

---
 src/app/app.module.ts                         |  4 +-
 src/app/home/home.component.html              |  1 +
 .../properties-upload.component.ts            |  3 +-
 .../taxlots-upload.component.html             |  8 ++++
 .../taxlots-upload.component.scss             |  0
 .../taxlots-upload.component.spec.ts          | 25 +++++++++++
 .../taxlots-upload.component.ts               | 42 +++++++++++++++++++
 7 files changed, 81 insertions(+), 2 deletions(-)
 create mode 100644 src/app/taxlots-upload/taxlots-upload.component.html
 create mode 100644 src/app/taxlots-upload/taxlots-upload.component.scss
 create mode 100644 src/app/taxlots-upload/taxlots-upload.component.spec.ts
 create mode 100644 src/app/taxlots-upload/taxlots-upload.component.ts

diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 66a79a4..7b16fb4 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -21,6 +21,7 @@ import { SharedModule } from './shared/shared.module';
 import { MapComponent } from './map/map.component';
 import { InventoryComponent } from './inventory/inventory.component';
 import { PropertiesUploadComponent } from './properties-upload/properties-upload.component';
+import { TaxlotsUploadComponent } from './taxlots-upload/taxlots-upload.component';
 
 // AoT requires an exported function for factories
 export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
@@ -35,7 +36,8 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
     LeftNavComponent,
     MapComponent,
     InventoryComponent,
-    PropertiesUploadComponent
+    PropertiesUploadComponent,
+    TaxlotsUploadComponent
   ],
   imports: [
     AppRoutingModule,
diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html
index 83a27b1..91462b7 100644
--- a/src/app/home/home.component.html
+++ b/src/app/home/home.component.html
@@ -17,4 +17,5 @@ <h1 class="title">Running: {{ status.running }}</h1>
   <textarea disabled>{{ latestQueryResult }}</textarea>
   
   <app-properties-upload></app-properties-upload>
+  <app-taxlots-upload></app-taxlots-upload>
 </div>
diff --git a/src/app/properties-upload/properties-upload.component.ts b/src/app/properties-upload/properties-upload.component.ts
index d0fd403..be787e5 100644
--- a/src/app/properties-upload/properties-upload.component.ts
+++ b/src/app/properties-upload/properties-upload.component.ts
@@ -8,7 +8,8 @@ import { PropertyService } from '../core/services/property/property.service';
   styleUrls: ['./properties-upload.component.scss']
 })
 export class PropertiesUploadComponent implements OnInit {
-  constructor(private propertyService: PropertyService) {}
+  
+  constructor(private propertyService: PropertyService) { }
 
   ngOnInit(): void {
   }
diff --git a/src/app/taxlots-upload/taxlots-upload.component.html b/src/app/taxlots-upload/taxlots-upload.component.html
new file mode 100644
index 0000000..7cfd675
--- /dev/null
+++ b/src/app/taxlots-upload/taxlots-upload.component.html
@@ -0,0 +1,8 @@
+<div>
+    <label for="file">Choose tax lot GeoJSON file</label>
+    <input 
+        type="file"
+        id="file"
+        accept=".geojson"
+        (change)="handleFileInput($event.target.files)">
+</div>
diff --git a/src/app/taxlots-upload/taxlots-upload.component.scss b/src/app/taxlots-upload/taxlots-upload.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/taxlots-upload/taxlots-upload.component.spec.ts b/src/app/taxlots-upload/taxlots-upload.component.spec.ts
new file mode 100644
index 0000000..305003b
--- /dev/null
+++ b/src/app/taxlots-upload/taxlots-upload.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TaxlotsUploadComponent } from './taxlots-upload.component';
+
+describe('TaxlotsUploadComponent', () => {
+  let component: TaxlotsUploadComponent;
+  let fixture: ComponentFixture<TaxlotsUploadComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ TaxlotsUploadComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(TaxlotsUploadComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/app/taxlots-upload/taxlots-upload.component.ts b/src/app/taxlots-upload/taxlots-upload.component.ts
new file mode 100644
index 0000000..33b8782
--- /dev/null
+++ b/src/app/taxlots-upload/taxlots-upload.component.ts
@@ -0,0 +1,42 @@
+import { Component, OnInit } from '@angular/core';
+import { TaxLotService } from '../core/services/tax-lot/tax-lot.service';
+
+@Component({
+  selector: 'app-taxlots-upload',
+  templateUrl: './taxlots-upload.component.html',
+  styleUrls: ['./taxlots-upload.component.scss']
+})
+export class TaxlotsUploadComponent implements OnInit {
+
+  constructor(private taxlotService: TaxLotService) { }
+
+  ngOnInit(): void {
+  }
+
+  handleFileInput(files: FileList): void {
+    if (files[0]) {
+      files[0].text().then(text => {
+        const geojson = JSON.parse(text);
+        const crs = geojson.crs ? geojson.crs : { type: 'name', properties: { name: 'EPSG:4326'} };
+
+        geojson.features.forEach(feature => {
+          const extra_data = feature.properties;
+          feature.geometry.crs = crs;
+          
+          // extract non-extra_data attributes
+          const ubid = extra_data.UBID;
+          delete extra_data.UBID;
+          
+          this.taxlotService.model.create({
+            extra_data: extra_data,
+            footprint: feature.geometry,
+            ulid: ubid
+          }).catch((err) => { 
+            console.error(err);
+          });
+        });
+      })
+    }
+  }
+
+}

From 542cea4d90779fe7a30582d7345bdd75662a2b5a Mon Sep 17 00:00:00 2001
From: Adrian Lara <adrian@devetry.com>
Date: Wed, 14 Oct 2020 09:43:52 -0400
Subject: [PATCH 3/3] Inventory map page tweaks

- fix axis order
- show extra_data to help with analysis
---
 src/app/map/map.component.ts | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/src/app/map/map.component.ts b/src/app/map/map.component.ts
index 3ada372..95e8c8d 100644
--- a/src/app/map/map.component.ts
+++ b/src/app/map/map.component.ts
@@ -53,12 +53,12 @@ export class MapComponent implements OnInit {
       values[0].forEach(property => {
         if (property.footprint) {
           const footprintCoords = property.footprint.coordinates[0].map(coords => {
-            return new Microsoft.Maps.Location(...coords)
+            return new Microsoft.Maps.Location(...coords.reverse())
           })
           const polygon = new Microsoft.Maps.Polygon(footprintCoords);
           const infobox = new Microsoft.Maps.Infobox(footprintCoords[0], {
             title: 'Property Information',
-            description: property.ubid,
+            description: JSON.stringify(property.extra_data),
             visible: false
           });
           infobox.setMap(this.map);
@@ -80,12 +80,12 @@ export class MapComponent implements OnInit {
       values[1].forEach(taxlot => {
         if (taxlot.footprint) {
           const footprintCoords = taxlot.footprint.coordinates[0].map(coords => {
-            return new Microsoft.Maps.Location(...coords)
+            return new Microsoft.Maps.Location(...coords.reverse())
           })
           const polygon = new Microsoft.Maps.Polygon(footprintCoords, this.styles.polygonOptions);
           const infobox = new Microsoft.Maps.Infobox(footprintCoords[0], {
             title: 'Tax Lot Information',
-            description: taxlot.ulid,
+            description: JSON.stringify(taxlot.extra_data),
             visible: false
           });
           infobox.setMap(this.map);
@@ -106,10 +106,12 @@ export class MapComponent implements OnInit {
       this.map.layers.insert(propertyLayer);
       this.map.layers.insert(taxlotLayer);
 
-      // allFootprints used because can't find attribute of map or layers to get shapes
-      this.map.setView({
-        bounds: new Microsoft.Maps.LocationRect.fromShapes(allFootprints)
-      })
+      if (allFootprints.length) {
+        // allFootprints used because can't find attribute of map or layers to get shapes
+        this.map.setView({
+          bounds: new Microsoft.Maps.LocationRect.fromShapes(allFootprints)
+        });
+      }
     })
   }
 }