Skip to content

Commit c8483ca

Browse files
committed
feat: added ability to create map from GPX file
1 parent d7503ef commit c8483ca

File tree

3 files changed

+156
-1
lines changed

3 files changed

+156
-1
lines changed

app/Http/Controllers/MapController.php

+58
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@
55
use App\Helpers\MapImageGenerator;
66
use App\Http\Resources\MapResource;
77
use App\Models\Map;
8+
use App\Parsers\Files\GeoJSONParser;
9+
use App\Parsers\Files\GPXParser;
810
use Illuminate\Http\Request;
911
use Illuminate\Support\Facades\View;
12+
use Illuminate\Validation\Rules\File;
13+
use Illuminate\Support\Str;
1014

1115
class MapController extends Controller
1216
{
@@ -130,6 +134,60 @@ public function store(Request $request)
130134
}
131135
}
132136

137+
/**
138+
* Store a map from a file
139+
*
140+
* @param \Illuminate\Http\Request $request
141+
* @return \Illuminate\Http\Response
142+
*
143+
* @deprecated Not yet implemented
144+
* @todo Implement this method
145+
*/
146+
public function storeFromFile(Request $request)
147+
{
148+
$this->authorize('create', Map::class);
149+
150+
$request->validate([
151+
'file' => [
152+
'required',
153+
File::types(['gpx', 'geojson'])
154+
->max(1024 * 3)
155+
],
156+
]);
157+
158+
// If the file is a GPX file, parse it and return the markers
159+
$fileMimeType = $request->file('file')->getMimeType();
160+
161+
if (Str::contains($fileMimeType, 'gpx')) {
162+
$parser = new GPXParser();
163+
} elseif (Str::contains($fileMimeType, 'json')) {
164+
$parser = new GeoJSONParser();
165+
} else {
166+
return response()->json(['error' => 'File type not supported'], 422);
167+
}
168+
169+
// Spread map and markers
170+
$parsedData = $parser->parseFile($request->file('file')->getRealPath());
171+
172+
// Create a map from the $parsedData['map'] and attach the markers
173+
$map = Map::create($parsedData['map']);
174+
175+
// Attach the markers to the request
176+
$request->merge(['markers' => $parsedData['markers']]);
177+
178+
// Call the Marker Controller to store the markers
179+
$markerController = new MarkerController();
180+
try {
181+
$markerController->storeInBulk($request, $map);
182+
return response()->json(['uuid' => $map->uuid, 'token' => $map->token], 201);
183+
} catch (\Exception $e) {
184+
$map->delete();
185+
return response()->json(['error' => 'Error while saving markers'], 500);
186+
}
187+
188+
return response()->json(['error' => 'Error while saving map'], 500);
189+
}
190+
133191
/**
134192
* Display the specified resource.
135193
*

routes/api.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
Route::get('maps/search', 'MapController@search');
3535

36-
// Route::post('maps/file', 'MapController@storeFromFile')->middleware('auth:api');
36+
Route::post('maps/file', 'MapController@storeFromFile')->middleware('auth:api');
3737

3838
Route::apiResource('maps', 'MapController');
3939

tests/Unit/MapTest.php

+97
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33
namespace Tests\Unit;
44

55
use App\Helpers\MapImageGenerator;
6+
use App\Jobs\FillMissingLocationGeocodes;
7+
use App\Jobs\FillMissingMarkerElevation;
8+
use App\Models\User;
69
use MatanYadaev\EloquentSpatial\Objects\Point;
710
use Tests\TestCase;
11+
use Illuminate\Http\UploadedFile;
12+
use Illuminate\Support\Facades\DB;
813

914
class MapTest extends TestCase
1015
{
@@ -191,6 +196,98 @@ public function testCreateMapTest()
191196
]);
192197
}
193198

199+
/**
200+
* Test create a map from a GPX file.
201+
*
202+
* @return void
203+
*/
204+
public function testCreateMapFromGpxTest()
205+
{
206+
$this->withoutExceptionHandling();
207+
208+
// Skip any dispatched jobs
209+
$this->expectsJobs([
210+
FillMissingMarkerElevation::class,
211+
FillMissingLocationGeocodes::class,
212+
]);
213+
214+
// We need to clean up the database before we start
215+
DB::table('markers')->delete();
216+
DB::table('marker_locations')->delete();
217+
218+
$gpx = file_get_contents(base_path('tests/fixtures/ashland.gpx'));
219+
220+
$file = UploadedFile::fake()->createWithContent('ashland.gpx', $gpx);
221+
222+
$user = User::factory()->create();
223+
224+
/**
225+
* @var \Illuminate\Contracts\Auth\Authenticatable
226+
*/
227+
$user = $user->givePermissionTo('create markers in bulk');
228+
229+
$this->actingAs($user, 'api');
230+
231+
$response = $this->postJson('/api/maps/file', [
232+
'file' => $file,
233+
'user_id' => $user->id,
234+
]);
235+
236+
// Assert returns 201
237+
$response->assertStatus(201);
238+
239+
// Assert that a map has been created
240+
$this->assertDatabaseHas('maps', [
241+
'title' => 'Rockbuster Duathlon at Ashland State Park',
242+
'description' => 'Team TopoGrafix tracklogs from the Rockbuster Duathlon at Ashland State Park, April 21st, 2002. The course consisted of a two-mile run, an seven mile mountain bike course, and a final two-mile run.
243+
244+
Braxton carried an eTrex Venture in his Camelbak for the three laps on the mountain bike loop. Vil carried his new eTrex Venture on the first run, but the GPS shut off during the first mountain bike loop due to battery vibration.
245+
',
246+
]);
247+
248+
// Assert that the response contains the map token
249+
$response->assertJsonStructure([
250+
'uuid',
251+
'token',
252+
]);
253+
254+
// Get the map by its uuid
255+
$map = \App\Models\Map::where('uuid', $response->json('uuid'))->first();
256+
257+
// The ashland DB file shoulda add 11 </trk>, 25 wpt, so we should have 36 markers
258+
$this->assertEquals(36, $map->markers()->count());
259+
260+
// The DB file adds 348 trkpt, so we should have 348 locations + 25 wpt
261+
$this->assertEquals(373, $map->markerLocations()->count());
262+
263+
// 271 of the locations should have an elevation
264+
$this->assertEquals(271, $map->markerLocations()->whereNotNull('elevation')->count());
265+
266+
// 267 should have created_at and updated_at on 2002-04-21 (any time)
267+
$this->assertEquals(267, $map->markerLocations()->whereDate('marker_locations.created_at', '2002-04-21')->whereDate('marker_locations.updated_at', '2002-04-21')->count());
268+
269+
// The others should have todays date
270+
$this->assertEquals(106, $map->markerLocations()->whereDate('marker_locations.created_at', now()->toDateString())->whereDate('marker_locations.updated_at', now()->toDateString())->count());
271+
272+
// There should be 11 markers with the field "number". It doesn't matter what the value is, just that it exists. This field will be a key in the JSON column called "meta"
273+
$this->assertEquals(11, $map->markers()->whereNotNull('meta->number')->count());
274+
275+
// The first marker ordered by ID should only have 1 location
276+
$this->assertEquals(1, $map->markers()->orderBy('id', 'asc')->first()->locations()->count());
277+
278+
// The first marker (ordered by ID) should have a current location of lat="42.246950" lon="-71.461807"
279+
$this->assertEquals(42.246950, $map->markers()->orderBy('id', 'asc')->first()->currentLocation->y);
280+
$this->assertEquals(-71.461807, $map->markers()->orderBy('id', 'asc')->first()->currentLocation->x);
281+
282+
// The last marker (ordered by ID) should have a current location of lat="42.244620" lon="-71.468704", and elevation of 63.584351
283+
$this->assertEquals(42.244620, $map->markers()->orderBy('id', 'desc')->first()->currentLocation->y);
284+
$this->assertEquals(-71.468704, $map->markers()->orderBy('id', 'desc')->first()->currentLocation->x);
285+
// We must round up from 63.584351 to 64 for the elevation
286+
$this->assertEquals(64, $map->markers()->orderBy('id', 'desc')->first()->currentLocation->z);
287+
// The last marker should have a total of 59 locations
288+
$this->assertEquals(59, $map->markers()->orderBy('id', 'desc')->first()->locations()->count());
289+
}
290+
194291
/**
195292
* Test claiming a map
196293
*

0 commit comments

Comments
 (0)