Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,6 @@ MY_HTTPS_PROXY=
MY_USER_ID=1000
MY_GROUP_ID=1000

# Default reference point in geo coordinates
DEFAULT_LONGITUDE=
DEFAULT_LATITUDE=
DEFAULT_HEIGHT=0

# Shared folder where maps are stored (default ${HOME}/data/maps)
MY_SHARED_MAPS_DIR=${HOME}/data/maps

Expand Down Expand Up @@ -179,7 +174,7 @@ Once you recorded a sequence, go to the iOS Files app on your phone, find the fi
Once you uploaded your image sequence dataset, it will get a unique ID assigned, it gets extracted, the video gets converted to an image sequences, the images get resized and rotated, and a thumbnail gets assigned to the dataset. Now you can start the map building process by pressing the play button. This might take around 10-30 minutes depending on the size of your recording.

### MapAligner
Once the map is built, more buttons will appear in the dataset's GUI. Next, you need to align the map with the world by clicking on the cross arrows icon, which opens the MapAligner with the corresponding map. First you need to define the coarse location by navigation the map view to your mapped street. Open the 'set reference origin' panel and check 'set reference origin with click'. Click on any point that you want to be the reference of the alignment, it can be any point in the neighborhood. Next, perform the map alignment with the gizmo. You can adjust the scale with the sliders in the left side menu. Finally, press the big green save button, this writes the transform to the MapBuilder.
Once the map is built, more buttons will appear in the dataset's GUI. Next, you need to align the map with the world by clicking on the cross arrows icon, which opens the MapAligner with the corresponding map. First you need to define the coarse location by navigation the map view to your mapped street. Open the 'set georeference origin' panel and check 'set georeference origin with click'. Click on any point that you want to be the reference of the alignment, it can be any point in the neighborhood. Next, perform the map alignment with the gizmo. You can adjust the scale with the sliders in the left side menu. Finally, press the big green save button, this writes the transform to the MapBuilder.

### MapLocalizer
Once your map is aligned with the world, it is ready to be served in the MapLocalizer. Click on the localization pin icon of the dataset's GUI, which loads the map into the MapLocalizer. This might take up to 30 seconds depending on the map size. Once the map is loaded, you can send GeoPoseRequest queries to the MapLocalizer's `/localize/geopose` endpoint.
Expand Down
9 changes: 0 additions & 9 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@ services:
- AUTH_FUSIONAUTH_ISSUER=${AUTH_FUSIONAUTH_ISSUER}
- AUTH_SECRET=${AUTH_SECRET}
- MAPLOCALIZER_URL=http://maplocalizer:8000
- DEFAULT_LONGITUDE=${DEFAULT_LONGITUDE}
- DEFAULT_LATITUDE=${DEFAULT_LATITUDE}
- DEFAULT_HEIGHT=${DEFAULT_HEIGHT}
- NODE_TLS_REJECT_UNAUTHORIZED=0
volumes:
- ${MY_SHARED_MAPS_DIR}:/uploads
Expand All @@ -54,9 +51,6 @@ services:
args:
- HTTP_PROXY=${MY_HTTP_PROXY}
- HTTPS_PROXY=${MY_HTTPS_PROXY}
- NEXT_PUBLIC_DEFAULT_LONGITUDE=${DEFAULT_LONGITUDE}
- NEXT_PUBLIC_DEFAULT_LATITUDE=${DEFAULT_LATITUDE}
- NEXT_PUBLIC_DEFAULT_HEIGHT=${DEFAULT_HEIGHT}
depends_on:
backend:
condition: service_healthy
Expand All @@ -68,9 +62,6 @@ services:
- AUTH_URL=${MAPALIGNER_URL}
- AUTH_TRUST_HOST=1
- MAPBUILDER_URL=${MAPBUILDER_URL}
- NEXT_PUBLIC_DEFAULT_LONGITUDE=${DEFAULT_LONGITUDE}
- NEXT_PUBLIC_DEFAULT_LATITUDE=${DEFAULT_LATITUDE}
- NEXT_PUBLIC_DEFAULT_HEIGHT=${DEFAULT_HEIGHT}
- NODE_TLS_REJECT_UNAUTHORIZED=0
deploy:
restart_policy:
Expand Down
18 changes: 1 addition & 17 deletions mapaligner/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,6 @@ COPY . .
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED=1

ARG NEXT_PUBLIC_DEFAULT_LATITUDE
ARG NEXT_PUBLIC_DEFAULT_LONGITUDE
ARG NEXT_PUBLIC_DEFAULT_HEIGHT

ENV NEXT_PUBLIC_DEFAULT_LATITUDE=${NEXT_PUBLIC_DEFAULT_LATITUDE}
ENV NEXT_PUBLIC_DEFAULT_LONGITUDE=${NEXT_PUBLIC_DEFAULT_LONGITUDE}
ENV NEXT_PUBLIC_DEFAULT_HEIGHT=${NEXT_PUBLIC_DEFAULT_HEIGHT}

RUN \
if [ -f yarn.lock ]; then yarn run build; \
elif [ -f package-lock.json ]; then npm run build; \
Expand Down Expand Up @@ -81,15 +73,7 @@ EXPOSE 3001

ENV PORT=3001

ARG NEXT_PUBLIC_DEFAULT_LATITUDE
ARG NEXT_PUBLIC_DEFAULT_LONGITUDE
ARG NEXT_PUBLIC_DEFAULT_HEIGHT

ENV NEXT_PUBLIC_DEFAULT_LATITUDE=${NEXT_PUBLIC_DEFAULT_LATITUDE}
ENV NEXT_PUBLIC_DEFAULT_LONGITUDE=${NEXT_PUBLIC_DEFAULT_LONGITUDE}
ENV NEXT_PUBLIC_DEFAULT_HEIGHT=${NEXT_PUBLIC_DEFAULT_HEIGHT}

# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/config/next-config-js/output
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
CMD ["node", "server.js"]
4 changes: 0 additions & 4 deletions mapaligner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,6 @@ AUTH_FUSIONAUTH_ISSUER=
# URL of mapbuilder to fetch the maps
MAPBUILDER_URL=

# Default start location
NEXT_PUBLIC_DEFAULT_LATITUDE=
NEXT_PUBLIC_DEFAULT_LONGITUDE=
NEXT_PUBLIC_DEFAULT_HEIGHT=
```

2. Generate Auth.js secret (must be the same for MapBuilder and MapAligner)
Expand Down
150 changes: 111 additions & 39 deletions mapaligner/app/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function reshape4x4(seq) {
return output;
}

/** @typedef {{matrix:number[][], longitude:number, latitude:number, height:number}} Data */
/** @typedef {{matrix:number[][], longitude:number|null, latitude:number|null, height:number|null}} Data */

/** @typedef {{id: string, name: string, size:number}} DataSet */

Expand All @@ -42,13 +42,22 @@ export default function Matcher() {
const [pointSize, setPointSize] = useState(1);
const [progress, setProgress] = useState(0);

const initialLatitude = parseFloat(process.env.NEXT_PUBLIC_DEFAULT_LATITUDE);
const initialLongitude = parseFloat(process.env.NEXT_PUBLIC_DEFAULT_LONGITUDE);
const initialHeight = parseFloat(process.env.NEXT_PUBLIC_DEFAULT_HEIGHT ?? "0");
const [latitude, setLatitude] = useState(NaN);
const [longitude, setLongitude] = useState(NaN);
const [height, setHeight] = useState(NaN);

const [latitude, setLatitude] = useState(initialLatitude);
const [longitude, setLongitude] = useState(initialLongitude);
const [height, setHeight] = useState(initialHeight);
function setGeoreference(nextLatitude, nextLongitude, nextHeight) {
const values = [nextLatitude, nextLongitude, nextHeight];
if (values.some((value) => !Number.isFinite(value))) {
setLatitude(NaN);
setLongitude(NaN);
setHeight(NaN);
return;
}
setLatitude(nextLatitude);
setLongitude(nextLongitude);
setHeight(nextHeight);
}

const [refMouseEnabled, setRefMouseEnabled] = useState(false);

Expand Down Expand Up @@ -111,12 +120,16 @@ export default function Matcher() {
function pickReference(event) {
if (refMouseEnabled) {
const { lat, lng } = event.lngLat;
setLongitude(lng);
setLatitude(lat);
setGeoreference(lat, lng, Number.isFinite(height) ? height : 0);
setRefMouseEnabled(false);
}
}

function clearReference() {
setGeoreference(NaN, NaN, NaN);
setRefMouseEnabled(false);
}

/*console.log("longitude", longitude);
console.log("latitude", latitude);
console.log("translation", translation);
Expand Down Expand Up @@ -148,6 +161,23 @@ export default function Matcher() {
console.error("No map selected");
return;
}
const geoFields = [latitude, longitude, height];
const numGeoFieldsSet = geoFields.filter((v) => Number.isFinite(v)).length;
if (numGeoFieldsSet !== 0 && numGeoFieldsSet !== geoFields.length) {
alert(
"Set all of latitude, longitude and height, or clear all three (no geo anchor). " +
"maplocalizer rejects a partially set georeference origin."
);
return;
}
if (Number.isFinite(latitude) && (latitude < -90 || latitude > 90)) {
alert("Latitude must be between -90 and 90.");
return;
}
if (Number.isFinite(longitude) && (longitude < -180 || longitude > 180)) {
alert("Longitude must be between -180 and 180.");
return;
}
console.log(output);
const params = new URLSearchParams({ type: selectedMap.type, dataSetId: selectedMap.dataSetId });
const saveQuery = await fetch(`/api/maps/${selectedMap.id}/transform?${params}`, {
Expand Down Expand Up @@ -180,9 +210,7 @@ export default function Matcher() {
credentials: "include",
}).then((response) => response.json());

setLatitude(data.latitude);
setLongitude(data.longitude);
setHeight(data.height);
setGeoreference(data.latitude ?? NaN, data.longitude ?? NaN, data.height ?? NaN);
const matrix = new Matrix4().fromArray(data.matrix.flat()).transpose();
const position = new Vector3();
const quaternion = new Quaternion();
Expand Down Expand Up @@ -225,21 +253,22 @@ export default function Matcher() {
setTranslation={setTranslation}
setRotation={setRotation}
setScale={setScale}
setLatitude={setLatitude}
setLongitude={setLongitude}
setGeoreference={setGeoreference}
/>
<details className="collapse collapse-arrow border border-base-300 dark:border-neutral">
<summary className="collapse-title">Set reference origin</summary>
<summary className="collapse-title">Set georeference origin</summary>
<div className="collapse-content flex flex-col gap-2">
<ReferenceInput
longitude={longitude}
latitude={latitude}
height={height}
setLongitude={setLongitude}
setLatitude={setLatitude}
setHeight={setHeight}
setGeoreference={setGeoreference}
/>
<ReferenceMouse
enabled={refMouseEnabled}
setEnabled={setRefMouseEnabled}
onClear={clearReference}
/>
<ReferenceMouse enabled={refMouseEnabled} setEnabled={setRefMouseEnabled} />
</div>
</details>
<details className="collapse collapse-arrow border border-base-300 dark:border-neutral">
Expand Down Expand Up @@ -355,7 +384,7 @@ function LoadFile({ maps, selectedMap, setSelectedMap, progress, modified }) {
);
}

function LoadConfig({ setTranslation, setRotation, setScale, setLatitude, setLongitude }) {
function LoadConfig({ setTranslation, setRotation, setScale, setGeoreference }) {
const textArea = useRef(null);
function load() {
/** @type {Data} */
Expand All @@ -368,8 +397,7 @@ function LoadConfig({ setTranslation, setRotation, setScale, setLatitude, setLon
setTranslation(translation);
setRotation(new Euler().setFromQuaternion(quaternion));
setScale(scale.x);
setLatitude(input.latitude);
setLongitude(input.longitude);
setGeoreference(input.latitude ?? NaN, input.longitude ?? NaN, input.height ?? NaN);
}
return (
<>
Expand All @@ -390,51 +418,95 @@ function LoadConfig({ setTranslation, setRotation, setScale, setLatitude, setLon
);
}

function ReferenceInput({ longitude, latitude, height, setLongitude, setLatitude, setHeight }) {
function ReferenceInput({
longitude,
latitude,
height,
setGeoreference,
}) {
const [latitudeText, setLatitudeText] = useState(formatReferenceValue(latitude));
const [longitudeText, setLongitudeText] = useState(formatReferenceValue(longitude));
const [heightText, setHeightText] = useState(formatReferenceValue(height));

useEffect(() => setLatitudeText(formatReferenceValue(latitude)), [latitude]);
useEffect(() => setLongitudeText(formatReferenceValue(longitude)), [longitude]);
useEffect(() => setHeightText(formatReferenceValue(height)), [height]);

function commitReferenceValues() {
const values = [latitudeText, longitudeText, heightText].map((value) => Number(value));
if (values.some((value) => !Number.isFinite(value))) {
setGeoreference(NaN, NaN, NaN);
return;
}
if (values[0] < -90 || values[0] > 90) {
alert("Latitude must be between -90 and 90.");
setGeoreference(NaN, NaN, NaN);
return;
}
if (values[1] < -180 || values[1] > 180) {
alert("Longitude must be between -180 and 180.");
setGeoreference(NaN, NaN, NaN);
return;
}
setGeoreference(...values);
}

return (
<>
<div className="grid grid-cols-[min-content_auto] gap-3 items-center">
<div className="text-nowrap">Ref. latitude:</div>
<div>
<input
className="input input-bordered w-full"
value={latitude}
onChange={(event) => setLatitude(parseFloat(event.target.value || "0"))}
value={latitudeText}
onChange={(event) => setLatitudeText(event.target.value)}
onBlur={commitReferenceValues}
/>
</div>
<div className="text-nowrap">Ref. longitude:</div>
<div>
<input
className="input input-bordered w-full"
value={longitude}
onChange={(event) => setLongitude(parseFloat(event.target.value || "0"))}
value={longitudeText}
onChange={(event) => setLongitudeText(event.target.value)}
onBlur={commitReferenceValues}
/>
</div>
<div className="text-nowrap">Ref. height:</div>
<div>
<input
className="input input-bordered w-full"
value={height}
onChange={(event) => setHeight(parseFloat(event.target.value || "0"))}
value={heightText}
onChange={(event) => setHeightText(event.target.value)}
onBlur={commitReferenceValues}
/>
</div>
</div>
</>
);
}

function ReferenceMouse({ enabled, setEnabled }) {
function formatReferenceValue(value) {
return Number.isFinite(value) ? String(value) : "NaN";
}

function ReferenceMouse({ enabled, setEnabled, onClear }) {
return (
<>
<label className="label cursor-pointer justify-start gap-3">
<input
type="checkbox"
className="checkbox"
checked={enabled}
onChange={(event) => setEnabled(event.target.checked)}
/>
<span>Set reference origin with mouse click</span>
</label>
<div className="flex items-center gap-3">
<button type="button" className="btn btn-sm" onClick={onClear}>
Clear
</button>
<label className="label cursor-pointer justify-start gap-3">
<input
type="checkbox"
className="checkbox"
checked={enabled}
onChange={(event) => setEnabled(event.target.checked)}
/>
<span>Set origin with mouse click</span>
</label>
</div>
</>
);
}
Expand Down
Loading