map: fix init position

Signed-off-by: Varun Patil <varunpatil@ucla.edu>
pull/563/head
Varun Patil 2023-04-01 18:14:37 -07:00
parent 3bf525193d
commit 7c53248936
7 changed files with 104 additions and 16 deletions

View File

@ -60,6 +60,7 @@ return [
['name' => 'Tags#set', 'url' => '/api/tags/set/{id}', 'verb' => 'PATCH'], ['name' => 'Tags#set', 'url' => '/api/tags/set/{id}', 'verb' => 'PATCH'],
['name' => 'Map#clusters', 'url' => '/api/map/clusters', 'verb' => 'GET'], ['name' => 'Map#clusters', 'url' => '/api/map/clusters', 'verb' => 'GET'],
['name' => 'Map#init', 'url' => '/api/map/init', 'verb' => 'GET'],
['name' => 'Archive#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'], ['name' => 'Archive#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'],

View File

@ -65,4 +65,16 @@ class MapController extends GenericApiController
return new JSONResponse($clusters); return new JSONResponse($clusters);
}); });
} }
/**
* @NoAdminRequired
*/
public function init(): Http\Response
{
return Util::guardEx(function () {
return new JSONResponse([
'pos' => $this->timelineQuery->getMapInitialPosition(),
]);
});
}
} }

View File

@ -211,7 +211,7 @@ trait TimelineQueryDays
?TimelineRoot $root = null, ?TimelineRoot $root = null,
bool $recursive = true, bool $recursive = true,
bool $archive = false bool $archive = false
) { ): IQueryBuilder {
if (null === $root) { if (null === $root) {
$root = $this->root(); $root = $this->root();
} }

View File

@ -9,6 +9,8 @@ use OCP\IDBConnection;
trait TimelineQueryMap trait TimelineQueryMap
{ {
use TimelineQueryDays;
protected IDBConnection $connection; protected IDBConnection $connection;
public function transformMapBoundsFilter(IQueryBuilder &$query, bool $aggregate, string $bounds, string $table = 'm') public function transformMapBoundsFilter(IQueryBuilder &$query, bool $aggregate, string $bounds, string $table = 'm')
@ -121,4 +123,36 @@ trait TimelineQueryMap
return $files; return $files;
} }
/**
* Gets the suggested initial coordinates for the map.
* Uses the coordinates of the newest photo (by date).
*/
public function getMapInitialPosition(): ?array
{
$query = $this->connection->getQueryBuilder();
// SELECT coordinates
$query->select('m.lat', 'm.lon')->from('memories', 'm');
// WHERE this photo is in the user's requested folder recursively
$query = $this->joinFilecache($query);
// WHERE this photo has coordinates
$query->where($query->expr()->andX(
$query->expr()->isNotNull('m.lat'),
$query->expr()->isNotNull('m.lon')
));
// ORDER BY datetaken DESC
$query->orderBy('m.datetaken', 'DESC');
// LIMIT 1
$query->setMaxResults(1);
// FETCH coordinates
$coords = $this->executeQueryWithCTEs($query)->fetch();
return $coords ?: null;
}
} }

View File

@ -627,12 +627,13 @@ export default defineComponent({
} }
// Map Bounds // Map Bounds
if (this.$route.name === "map" && this.$route.query.b) { if (this.$route.name === "map") {
API.DAYS_FILTER( const bounds = <string>this.$route.query.b;
query, if (!bounds) {
DaysFilterType.MAP_BOUNDS, throw new Error("Missing map bounds");
<string>this.$route.query.b }
);
API.DAYS_FILTER(query, DaysFilterType.MAP_BOUNDS, bounds);
} }
// Month view // Month view
@ -699,11 +700,20 @@ export default defineComponent({
/** Fetch timeline main call */ /** Fetch timeline main call */
async fetchDays(noCache = false) { async fetchDays(noCache = false) {
// Get top folders if route // Awaiting this is important because the folders must render
// before the timeline to prevent glitches
await this.fetchFolders(); await this.fetchFolders();
// Get URL an cache identifier // Get URL an cache identifier
const url = API.Q(API.DAYS(), this.getQuery()); let url: string;
try {
url = API.Q(API.DAYS(), this.getQuery());
} catch (err) {
// Likely invalid route; just quit doing anything
return;
}
// URL for cached data
const cacheUrl = <string>this.$route.name + url; const cacheUrl = <string>this.$route.name + url;
// Try cache first // Try cache first

View File

@ -110,12 +110,7 @@ export default defineComponent({
map.mapObject.zoomControl.setPosition("topright"); map.mapObject.zoomControl.setPosition("topright");
// Initialize // Initialize
if (this.$route.query.b && this.$route.query.z) { this.initialize();
this.setBoundsFromQuery();
this.fetchClusters();
} else {
this.refresh();
}
// If currently dark mode, set isDark // If currently dark mode, set isDark
const pane = document.querySelector(".leaflet-tile-pane"); const pane = document.querySelector(".leaflet-tile-pane");
@ -143,11 +138,43 @@ export default defineComponent({
watch: { watch: {
$route() { $route() {
this.fetchClusters(); this.initialize(true);
}, },
}, },
methods: { methods: {
/**
* Get initial coordinates for display and set them.
* Then fetch clusters.
*/
async initialize(reinit: boolean = false) {
// Check if we have bounds and zoom in query
if (this.$route.query.b && this.$route.query.z) {
if (!reinit) {
this.setBoundsFromQuery();
}
return await this.fetchClusters();
}
// Otherwise, get location from server
try {
const init = await axios.get<any>(API.MAP_INIT());
// Init data contains position information
const map = this.$refs.map as LMap;
const pos = init?.data?.pos;
if (!pos?.lat || !pos?.lon) {
throw new Error("No position data");
}
// This will trigger route change -> fetchClusters
map.mapObject.setView([pos.lat, pos.lon], 11);
} catch (e) {
// Make sure we initialize clusters anyway
this.refresh();
}
},
async refreshDebounced() { async refreshDebounced() {
utils.setRenewingTimeout(this, "refreshTimer", this.refresh, 250); utils.setRenewingTimeout(this, "refreshTimer", this.refresh, 250);
}, },

View File

@ -188,4 +188,8 @@ export class API {
static MAP_CLUSTER_PREVIEW(id: number) { static MAP_CLUSTER_PREVIEW(id: number) {
return tok(gen(`${BASE}/map/clusters/preview/{id}`, { id })); return tok(gen(`${BASE}/map/clusters/preview/{id}`, { id }));
} }
static MAP_INIT() {
return tok(gen(`${BASE}/map/init`));
}
} }