Skip to content

Commit

Permalink
Merge pull request geonetwork#777 from geonetwork/mv-add-from-geojson
Browse files Browse the repository at this point in the history
[Map-Viewer] Added layer from a GeoJSON file
  • Loading branch information
ronitjadhav authored Jan 25, 2024
2 parents 177cac3 + 0c8d1af commit dc3a671
Show file tree
Hide file tree
Showing 14 changed files with 277 additions and 1 deletion.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div class="flex flex-col gap-2 my-2">
<div class="flex items-center gap-4">
<div class="flex-grow rounded-md border-2 border-gray-200">
<gn-ui-drag-and-drop-file-input
(fileChange)="handleFileChange($event)"
[accept]="acceptedMimeType.join(',')"
[placeholder]="'map.addFromFile.placeholder' | translate"
class="placeholder-grey"
></gn-ui-drag-and-drop-file-input>
</div>
</div>
<p class="text-sm text-gray-600" translate>map.help.addFromFile</p>
</div>

<div *ngIf="errorMessage" class="text-red-500 mt-2">
{{ errorMessage }}
</div>

<div *ngIf="successMessage" class="text-green-500 mt-2">
{{ successMessage }}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { AddLayerFromFileComponent } from './add-layer-from-file.component'
import { MapFacade } from '../+state/map.facade'
import { TranslateModule } from '@ngx-translate/core'

class MapFacadeMock {
addLayer = jest.fn()
}

describe('AddLayerFromFileComponent', () => {
let component: AddLayerFromFileComponent
let fixture: ComponentFixture<AddLayerFromFileComponent>
let mapFacade: MapFacade

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [AddLayerFromFileComponent],
providers: [
{
provide: MapFacade,
useClass: MapFacadeMock,
},
],
}).compileComponents()

mapFacade = TestBed.inject(MapFacade)
fixture = TestBed.createComponent(AddLayerFromFileComponent)
component = fixture.componentInstance
fixture.detectChanges()
})

it('should create', () => {
expect(component).toBeTruthy()
expect(component.errorMessage).toBeFalsy()
expect(component.loading).toBe(false)
expect(component.successMessage).toBeFalsy()
})

describe('handleFileChange', () => {
describe('if file is not selected', () => {
beforeEach(() => {
component.handleFileChange(null)
})
it('should set error message', () => {
expect(component.errorMessage).toEqual('Invalid file format')
})
})
describe('if file size exceeds the limit', () => {
beforeEach(() => {
const file = new File([''], 'filename', { type: 'text/plain' })
jest.spyOn(file, 'size', 'get').mockReturnValue(5000001)
component.handleFileChange(file)
})
it('should set error message', () => {
expect(component.errorMessage).toEqual(
'File size exceeds the limit of 5MB'
)
})
})
describe('if file format is invalid', () => {
beforeEach(() => {
const file = new File([''], 'filename', { type: 'text/plain' })
component.handleFileChange(file)
})
it('should set error message', () => {
expect(component.errorMessage).toEqual('Invalid file format')
})
})
describe('Invalid and then valid file', () => {
beforeEach(async () => {
const file = new File([''], 'filename', { type: 'text/plain' })
await component.handleFileChange(file).catch(() => {
// ignore
})
const file2 = new File([''], 'filename.geojson', {
type: 'application/json',
})
await component.handleFileChange(file2)
})
it('should show no error', () => {
expect(component.errorMessage).toBeFalsy()
})
})
})
describe('addGeoJsonLayer', () => {
let data // define data here

beforeEach(async () => {
// make this async
data = {
type: 'Feature',
properties: {
id: '0',
},
geometry: {
type: 'Polygon',
coordinates: [
[
[100.0, 0.0],
[101.0, 0.0],
[101.0, 1.0],
[100.0, 1.0],
[100.0, 0.0],
],
],
},
}

const blob = new Blob([JSON.stringify(data, null, 2)], {
type: 'application/json',
})
const file = new File([blob], 'filename.geojson', {
type: 'application/json',
})

await component.handleFileChange(file) // await this
})

it('should add the layer', () => {
expect(mapFacade.addLayer).toHaveBeenCalledWith({
type: 'geojson',
title: 'filename',
data: JSON.stringify(data, null, 2),
})
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { ChangeDetectorRef, Component } from '@angular/core'
import { MapContextLayerModel } from '../map-context/map-context.model'
import { MapFacade } from '../+state/map.facade'

const INVALID_FILE_FORMAT_ERROR_MESSAGE = 'Invalid file format'

@Component({
selector: 'gn-ui-add-layer-from-file',
templateUrl: './add-layer-from-file.component.html',
styleUrls: ['./add-layer-from-file.component.css'],
})
export class AddLayerFromFileComponent {
errorMessage: string | null = null
successMessage: string | null = null
loading = false
readonly acceptedMimeType = ['.geojson']
readonly maxFileSize = 5000000

constructor(
private mapFacade: MapFacade,
private changeDetectorRef: ChangeDetectorRef
) {}

async handleFileChange(file: File) {
if (!file) {
this.displayMessage(INVALID_FILE_FORMAT_ERROR_MESSAGE, 'error')
return
}
if (file.size > this.maxFileSize) {
this.displayMessage('File size exceeds the limit of 5MB', 'error')
return
}
await this.addLayer(file)
}

private async addLayer(file: File) {
this.errorMessage = null
this.loading = true
try {
if (!this.isFileFormatValid(file)) {
this.displayMessage(INVALID_FILE_FORMAT_ERROR_MESSAGE, 'error')
return
}

const fileExtension = this.getFileExtension(file)
switch (fileExtension) {
case 'geojson':
await this.addGeoJsonLayer(file)
break
default:
this.displayMessage(INVALID_FILE_FORMAT_ERROR_MESSAGE, 'error')
break
}
} catch (error) {
const err = error as Error
this.displayMessage('Error loading file: ' + err.message, 'error')
} finally {
this.loading = false
}
}

private addGeoJsonLayer(file: File) {
return new Promise<void>((resolve, reject) => {
try {
const reader = new FileReader()
reader.onload = () => {
const result = reader.result as string
const title = file.name.split('.').slice(0, -1).join('.')
const layerToAdd: MapContextLayerModel = {
type: 'geojson',
data: result,
}
this.mapFacade.addLayer({ ...layerToAdd, title: title })
this.displayMessage('File successfully added to map', 'success')
resolve()
}
reader.onerror = reject
reader.readAsText(file)
} catch (error) {
reject(error)
}
})
}

private isFileFormatValid(file: File): boolean {
const fileExtension = this.getFileExtension(file)
return this.acceptedMimeType.includes(`.${fileExtension}`)
}

private getFileExtension(file: File): string | undefined {
return file.name.split('.').pop()
}

private displayMessage(message: string, type: 'success' | 'error') {
if (type === 'success') {
this.successMessage = message
} else if (type === 'error') {
this.errorMessage = message
}

setTimeout(() => {
this.successMessage = null
this.errorMessage = null
this.changeDetectorRef.detectChanges()
}, 5000)
}
}
2 changes: 2 additions & 0 deletions libs/feature/map/src/lib/feature-map.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { AddLayerRecordPreviewComponent } from './add-layer-from-catalog/add-lay
import { UiElementsModule } from '@geonetwork-ui/ui/elements'
import { UiInputsModule } from '@geonetwork-ui/ui/inputs'
import { AddLayerFromWmsComponent } from './add-layer-from-wms/add-layer-from-wms.component'
import { AddLayerFromFileComponent } from './add-layer-from-file/add-layer-from-file.component'
import { AddLayerFromWfsComponent } from './add-layer-from-wfs/add-layer-from-wfs.component'

@NgModule({
Expand All @@ -32,6 +33,7 @@ import { AddLayerFromWfsComponent } from './add-layer-from-wfs/add-layer-from-wf
MapContainerComponent,
AddLayerRecordPreviewComponent,
AddLayerFromWmsComponent,
AddLayerFromFileComponent,
AddLayerFromWfsComponent,
],
exports: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
</div>
</mat-tab>
<mat-tab [label]="'map.add.layer.file' | translate" bodyClass="h-full">
<div class="p-3 h-full">Add from file</div>
<div class="p-3">
<gn-ui-add-layer-from-file></gn-ui-add-layer-from-file>
</div>
</mat-tab>
</mat-tab-group>
</gn-ui-expandable-panel-button>
Expand Down
2 changes: 2 additions & 0 deletions translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@
"map.add.layer.file": "Aus einer Datei",
"map.add.layer.wfs": "Aus WFS",
"map.add.layer.wms": "Aus WMS",
"map.addFromFile.placeholder": "",
"map.help.addFromFile": "",
"map.layer.add": "",
"map.layers.available": "",
"map.layers.list": "Ebenen",
Expand Down
2 changes: 2 additions & 0 deletions translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@
"map.add.layer.file": "From a file",
"map.add.layer.wfs": "From WFS",
"map.add.layer.wms": "From WMS",
"map.addFromFile.placeholder": "Click or drop a file here",
"map.help.addFromFile": "Click or drag and drop a file to add to the map (currently supports GeoJSON format only).",
"map.layer.add": "Add",
"map.layers.available": "Available Layers",
"map.layers.list": "Layers",
Expand Down
2 changes: 2 additions & 0 deletions translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@
"map.add.layer.file": "",
"map.add.layer.wfs": "",
"map.add.layer.wms": "",
"map.addFromFile.placeholder": "",
"map.help.addFromFile": "",
"map.layer.add": "",
"map.layers.available": "",
"map.layers.list": "",
Expand Down
2 changes: 2 additions & 0 deletions translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@
"map.add.layer.file": "",
"map.add.layer.wfs": "",
"map.add.layer.wms": "",
"map.addFromFile.placeholder": "",
"map.help.addFromFile": "",
"map.layer.add": "",
"map.layers.available": "",
"map.layers.list": "",
Expand Down
2 changes: 2 additions & 0 deletions translations/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@
"map.add.layer.file": "Da un file",
"map.add.layer.wfs": "Da un WFS",
"map.add.layer.wms": "Da un WMS",
"map.addFromFile.placeholder": "",
"map.help.addFromFile": "",
"map.layer.add": "",
"map.layers.available": "",
"map.layers.list": "Layers",
Expand Down
2 changes: 2 additions & 0 deletions translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@
"map.add.layer.file": "",
"map.add.layer.wfs": "",
"map.add.layer.wms": "",
"map.addFromFile.placeholder": "",
"map.help.addFromFile": "",
"map.layer.add": "",
"map.layers.available": "",
"map.layers.list": "",
Expand Down
2 changes: 2 additions & 0 deletions translations/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@
"map.add.layer.file": "",
"map.add.layer.wfs": "",
"map.add.layer.wms": "",
"map.addFromFile.placeholder": "",
"map.help.addFromFile": "",
"map.layer.add": "",
"map.layers.available": "",
"map.layers.list": "",
Expand Down
2 changes: 2 additions & 0 deletions translations/sk.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@
"map.add.layer.file": "Zo súboru",
"map.add.layer.wfs": "Z WFS",
"map.add.layer.wms": "Z WMS",
"map.addFromFile.placeholder": "",
"map.help.addFromFile": "",
"map.layer.add": "",
"map.layers.available": "",
"map.layers.list": "Vrstvy",
Expand Down

0 comments on commit dc3a671

Please sign in to comment.