Skip to content

Commit 8d3e6b1

Browse files
committed
feat: added geoJSON upload
1 parent 74ef571 commit 8d3e6b1

File tree

6 files changed

+2618
-14
lines changed

6 files changed

+2618
-14
lines changed

app/Http/Controllers/MarkerController.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use App\Models\Map;
88
use App\Models\Marker;
99
use App\Models\MarkerLocation;
10+
use App\Parsers\Files\GeoJSONParser;
1011
use App\Parsers\Files\GPXParser;
1112
use Carbon\Carbon;
1213
use MatanYadaev\EloquentSpatial\Objects\Point;
@@ -324,7 +325,7 @@ public function storeInBulkFromFile(Request $request, Map $map)
324325
$request->validate([
325326
'file' => [
326327
'required',
327-
File::types(['gpx'])
328+
File::types(['gpx', 'geojson'])
328329
->max(1024 * 3)
329330
],
330331
]);
@@ -334,6 +335,8 @@ public function storeInBulkFromFile(Request $request, Map $map)
334335

335336
if (Str::contains($fileMimeType, 'gpx')) {
336337
$parser = new GPXParser();
338+
} elseif (Str::contains($fileMimeType, 'json')) {
339+
$parser = new GeoJSONParser();
337340
} else {
338341
return response()->json(['error' => 'File type not supported'], 422);
339342
}

app/Parsers/Files/FIleParser.php

+8
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,12 @@ abstract public function parseFile(string $filepath): array;
1919
* @return array{category_name?: string, description?: string, link?: string, created_at?: string, updated_at?: string, meta: array, lat?: float, lng?: float, elevation?: float, locations?: array}
2020
*/
2121
abstract public function parseMarkersFromFile(string $filepath): array;
22+
23+
/**
24+
* Parse the map details from the file.
25+
*
26+
* @param string $filepath
27+
* @return array{title?: string, description?: string}
28+
*/
29+
abstract public function parseMapDetailsFromFile(string $filepath): array;
2230
}

app/Parsers/Files/GPXParser.php

+4-13
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,6 @@
44

55
class GPXParser extends FIleParser
66
{
7-
/**
8-
* Parse the GPX file.
9-
*
10-
* @param string $filepath
11-
* @return array
12-
*/
137
public function parseFile(string $filepath): array
148
{
159
return [
@@ -135,12 +129,6 @@ public function parseMarkersFromFile(string $filepath): array
135129
return $markers;
136130
}
137131

138-
/**
139-
* Parse the map details, like the name, description, author, etc.
140-
*
141-
* @param string $filepath
142-
* @return array
143-
*/
144132
public function parseMapDetailsFromFile(string $filepath): array
145133
{
146134
$xml = simplexml_load_file($filepath);
@@ -153,6 +141,9 @@ public function parseMapDetailsFromFile(string $filepath): array
153141
$mapDetails = $array['metadata'];
154142
}
155143

156-
return $mapDetails;
144+
return [
145+
'title' => $mapDetails['name'] ?? null,
146+
'description' => $mapDetails['desc'] ?? null,
147+
];
157148
}
158149
}

app/Parsers/Files/GeoJSONParser.php

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
namespace App\Parsers\Files;
4+
5+
class GeoJSONParser extends FIleParser
6+
{
7+
public function parseFile(string $filepath): array
8+
{
9+
return [
10+
'map' => $this->parseMapDetailsFromFile($filepath),
11+
'markers' => $this->parseMarkersFromFile($filepath),
12+
];
13+
}
14+
15+
public function parseMarkersFromFile(string $filepath): array
16+
{
17+
$json = file_get_contents($filepath);
18+
$data = json_decode($json, true);
19+
20+
$markers = [];
21+
22+
if (isset($data['features'])) {
23+
foreach ($data['features'] as $feature) {
24+
// Depending on the type of geometry, the coordinates can be in different places
25+
// If its just a point, the coordinates are in the first level of the geometry object
26+
if ($feature['geometry']['type'] === 'Point') {
27+
$markers[] = [
28+
'lat' => $feature['geometry']['coordinates'][1],
29+
'lng' => $feature['geometry']['coordinates'][0],
30+
'category_name' => $feature['properties']['name'] ?? 'Feature',
31+
'description' => $feature['properties']['description'] ?? null,
32+
'link' => $feature['properties']['link'] ?? null,
33+
'elevation' => $feature['properties']['elevation'] ?? null,
34+
'created_at' => $feature['properties']['time'] ?? null,
35+
'updated_at' => $feature['properties']['time'] ?? null,
36+
'meta' => $feature['properties'],
37+
];
38+
} elseif ($feature['geometry']['type'] === 'LineString') {
39+
// Else if its a lineString, we have to add it to markers.locations for each coordinate
40+
41+
$locations = [];
42+
43+
$pointers = 0;
44+
foreach ($feature['geometry']['coordinates'] as $coordinate) {
45+
$locations[] = [
46+
'lat' => $coordinate[1],
47+
'lng' => $coordinate[0],
48+
'elevation' => $coordinate[2] ?? null,
49+
50+
// There may be a coordTimes array in the properties, which we can use to set the created_at and updated_at
51+
'created_at' => $feature['properties']['coordTimes'][$pointers] ?? null,
52+
'updated_at' => $feature['properties']['coordTimes'][$pointers] ?? null,
53+
];
54+
}
55+
56+
$markers[] = [
57+
'category_name' => $feature['properties']['name'] ?? 'Feature',
58+
'description' => $feature['properties']['description'] ?? null,
59+
'link' => $feature['properties']['link'] ?? null,
60+
'created_at' => $feature['properties']['time'] ?? null,
61+
'updated_at' => $feature['properties']['time'] ?? null,
62+
'meta' => $feature['properties'],
63+
'locations' => $locations,
64+
];
65+
}
66+
}
67+
}
68+
69+
return $markers;
70+
}
71+
72+
public function parseMapDetailsFromFile(string $filepath): array
73+
{
74+
return [
75+
'name' => pathinfo($filepath, PATHINFO_FILENAME),
76+
'description' => 'A map of ' . pathinfo($filepath, PATHINFO_FILENAME),
77+
];
78+
}
79+
}

tests/Unit/MarkerTest.php

+51
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,57 @@ public function testCreateMarkerInBulkWithGpxFile()
454454
$this->assertEquals(11, $map->markers()->whereNotNull('meta->number')->count());
455455
}
456456

457+
public function testCreateMarkerInBulkWithGeoJSONFile()
458+
{
459+
$this->withoutExceptionHandling();
460+
461+
// Skip any dispatched jobs
462+
$this->expectsJobs([
463+
FillMissingMarkerElevation::class,
464+
FillMissingLocationGeocodes::class,
465+
]);
466+
467+
// We need to clean up the database before we start
468+
DB::table('markers')->delete();
469+
DB::table('marker_locations')->delete();
470+
471+
$map = new \App\Models\Map();
472+
$map->users_can_create_markers = 'yes';
473+
$map->options = ['links' => 'optional'];
474+
$map->save();
475+
476+
$user = User::factory()->create();
477+
478+
/**
479+
* @var \Illuminate\Contracts\Auth\Authenticatable
480+
*/
481+
$user = $user->givePermissionTo('create markers in bulk');
482+
483+
$this->actingAs($user, 'api');
484+
485+
$geojson = file_get_contents(base_path('tests/fixtures/ashland.geojson'));
486+
487+
$file = UploadedFile::fake()->createWithContent('ashland.geojson', $geojson);
488+
489+
$response = $this->postJson('/api/maps/' . $map->uuid . '/markers/file', ['file' => $file]);
490+
$response->assertStatus(200);
491+
492+
// The ashland DB file shoulda add 11 </trk>, 25 wpt, so we should have 36 markers
493+
$this->assertEquals(36, $map->markers()->count());
494+
495+
// The DB file adds 348 trkpt, so we should have 348 locations + 25 wpt
496+
$this->assertEquals(373, $map->markerLocations()->count());
497+
498+
// 271 of the locations should have an elevation - note its normal this one is lower than the GPX one, some of the elevations were lost in the conversion to GeoJSON
499+
$this->assertEquals(265, $map->markerLocations()->whereNotNull('elevation')->count());
500+
501+
// 267 should have created_at and updated_at on 2002-04-21 (any time)
502+
$this->assertEquals(267, $map->markerLocations()->whereDate('marker_locations.created_at', '2002-04-21')->whereDate('marker_locations.updated_at', '2002-04-21')->count());
503+
504+
// The others should have todays date
505+
$this->assertEquals(106, $map->markerLocations()->whereDate('marker_locations.created_at', now()->toDateString())->whereDate('marker_locations.updated_at', now()->toDateString())->count());
506+
}
507+
457508
/**
458509
* A basic test example.
459510
*

0 commit comments

Comments
 (0)