Merge pull request #13 from cartesiancs/develop

Develop
This commit is contained in:
Hyeong Jun Huh 2026-05-13 15:48:57 +09:00 committed by GitHub
commit 2c5d732477
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 114 additions and 88 deletions

View File

@ -1,9 +1,7 @@
import { useAreaStore } from "@/state/areaStore";
import { css, keyframes } from "@emotion/react"; import { css, keyframes } from "@emotion/react";
import { Loader2 } from "lucide-react"; import { Loader2 } from "lucide-react";
import React, { useState } from "react";
interface Building { export interface Building {
id: number; id: number;
tags: { [key: string]: string | undefined }; tags: { [key: string]: string | undefined };
geometry?: { lat: number; lng: number }[]; geometry?: { lat: number; lng: number }[];
@ -14,85 +12,37 @@ from { transform: rotate(0deg); }
to { transform: rotate(360deg); } to { transform: rotate(360deg); }
`; `;
export function BuildingHeights({ area }: { area: any }) { export function BuildingHeights({
const [buildings, setBuildings] = useState<Building[]>([]); buildings,
const [loading, setLoading] = useState(false); loading,
}: {
const appendAreas = useAreaStore((state) => state.appendAreas); buildings: Building[];
loading: boolean;
const requestBuildings = () => { }) {
setLoading(true);
const south = area[1].lat;
const west = area[1].lng;
const north = area[0].lat;
const east = area[0].lng;
console.log(south, west, north, east);
const query = `[out:json][timeout:25];(way["building"]( ${south},${west},${north},${east} );relation["building"]( ${south},${west},${north},${east} ););out body geom;`;
fetch("https://overpass-api.de/api/interpreter", {
method: "POST",
body: query,
headers: { "Content-Type": "application/x-www-form-urlencoded" },
})
.then((response) => response.json())
.then((data) => {
const blds: any = data.elements.map((element) => ({
id: element.id,
tags: element.tags,
geometry: element.geometry
? element.geometry.map((pt) => ({ lat: pt.lat, lng: pt.lon }))
: undefined,
}));
setBuildings(blds);
appendAreas(blds);
console.log("Building Data:", blds);
})
.catch((error) => {
console.error("Error fetching building data:", error);
})
.finally(() => {
setLoading(false);
});
};
return ( return (
<div <div
css={css({ css={css({
position: "relative", position: "relative",
})} })}
> >
<button {loading && (
css={css({ <div
color: "#ffffff", css={css({
display: "flex",
backgroundColor: "#007bffe8", alignItems: "center",
backdropFilter: "blur(8px)", gap: "0.5rem",
border: "none", color: "#555",
padding: "0.5rem 1rem", })}
borderRadius: "8px", >
outline: "#086ad4c2 solid 0.1rem",
cursor: "pointer",
transition: "0.2s",
display: "flex",
alignItems: "center",
gap: "0.5rem",
":hover": {
backgroundColor: "#085fbd",
},
})}
onClick={requestBuildings}
>
{loading && (
<Loader2 <Loader2
css={css({ css={css({
animation: `${spinAnimation} 1s linear infinite`, animation: `${spinAnimation} 1s linear infinite`,
})} })}
size={16} size={16}
/> />
)} Fetching building information...
request </div>
</button> )}
<ul <ul
css={css({ css={css({
overflow: "scroll", overflow: "scroll",

View File

@ -7,8 +7,8 @@ type AreaStore = {
lng: number; lng: number;
}[]; }[];
appendAreas: (areas: []) => void; appendAreas: (areas: any[]) => void;
setCenter: (center: []) => void; setCenter: (center: any[]) => void;
}; };
export const useAreaStore = create<AreaStore>((set) => ({ export const useAreaStore = create<AreaStore>((set) => ({

View File

@ -43,6 +43,7 @@ function Building({
e.stopPropagation(); e.stopPropagation();
}} }}
rotation={[-Math.PI / 2, 0, 0]} rotation={[-Math.PI / 2, 0, 0]}
userData={{ exportToGLB: true }}
> >
<extrudeGeometry args={[shape, extrudeSettings]} /> <extrudeGeometry args={[shape, extrudeSettings]} />
<meshStandardMaterial color={hovered || clicked ? "#007bff" : "#9da0a3"} /> <meshStandardMaterial color={hovered || clicked ? "#007bff" : "#9da0a3"} />
@ -353,7 +354,14 @@ function Roads({ area }: { area: any }) {
const lineGeometry: any = new THREE.BufferGeometry().setFromPoints(points); const lineGeometry: any = new THREE.BufferGeometry().setFromPoints(points);
return <Line points={points} color="#34f516" lineWidth={1}></Line>; return (
<Line
points={points}
color="#34f516"
lineWidth={1}
userData={{ exportToGLB: true }}
></Line>
);
})} })}
</> </>
); );
@ -391,15 +399,16 @@ export function Export() {
}; };
const exportGLB = () => { const exportGLB = () => {
const sceneClone = scene.clone(true); const exportRoot = new THREE.Group();
sceneClone.traverse((child) => { scene.traverse((child) => {
if (child.userData && child.userData.skipExport === true) child.parent?.remove(child); if (child.userData?.exportToGLB === true) {
if ((child as any).isHtml === true) child.parent?.remove(child); exportRoot.add(child.clone(true));
}
}); });
const exporter = new GLTFExporter(); const exporter = new GLTFExporter();
const options = { binary: true, embedImages: true }; const options = { binary: true, embedImages: true };
exporter.parse( exporter.parse(
sceneClone, exportRoot,
(result) => { (result) => {
if (result instanceof ArrayBuffer) { if (result instanceof ArrayBuffer) {
const blob = new Blob([result], { type: "model/gltf-binary" }); const blob = new Blob([result], { type: "model/gltf-binary" });

View File

@ -1,4 +1,4 @@
import { css } from "@emotion/react"; import { css, keyframes } from "@emotion/react";
import { Space } from "../three/Space"; import { Space } from "../three/Space";
import { FullscreenModal } from "../components/FullscreenModal"; import { FullscreenModal } from "../components/FullscreenModal";
import { Title } from "@/components/text/Title"; import { Title } from "@/components/text/Title";
@ -11,8 +11,8 @@ import {
NextButton, NextButton,
PrevButton, PrevButton,
} from "@/components/button/BottomButton"; } from "@/components/button/BottomButton";
import { BuildingHeights } from "@/components/map/Processing"; import { BuildingHeights, Building } from "@/components/map/Processing";
import { ChevronLeft, ChevronRight, Download } from "lucide-react"; import { ChevronLeft, ChevronRight, Download, Loader2 } from "lucide-react";
import { useAreaStore } from "@/state/areaStore"; import { useAreaStore } from "@/state/areaStore";
import { useActionStore } from "@/state/exportStore"; import { useActionStore } from "@/state/exportStore";
import { Modal } from "@/components/modal/Modal"; import { Modal } from "@/components/modal/Modal";
@ -26,6 +26,11 @@ const IconSize = css({
height: "14px", height: "14px",
}); });
const spinAnimation = keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`;
function App() { function App() {
const [isNextButtonDisabled, setIsNextButtonDisabled] = useState(true); const [isNextButtonDisabled, setIsNextButtonDisabled] = useState(true);
const [areaData, setAreaData] = useState([]); const [areaData, setAreaData] = useState([]);
@ -36,8 +41,12 @@ function App() {
const [isFleetLogin, setIsFleetLogin] = useState(false); const [isFleetLogin, setIsFleetLogin] = useState(false);
const [isFleetModal, setIsFleetModal] = useState(false); const [isFleetModal, setIsFleetModal] = useState(false);
const [spaceList, setSpaceList] = useState([]); const [spaceList, setSpaceList] = useState([]);
const [buildings, setBuildings] = useState<Building[]>([]);
const [isFetchingBuildings, setIsFetchingBuildings] = useState(false);
const [hasFetchedBuildings, setHasFetchedBuildings] = useState(false);
const setCenter = useAreaStore((state) => state.setCenter); const setCenter = useAreaStore((state) => state.setCenter);
const appendAreas = useAreaStore((state) => state.appendAreas);
const setAction = useActionStore((state) => state.setAction); const setAction = useActionStore((state) => state.setAction);
const setFleet = useActionStore((state) => state.setFleet); const setFleet = useActionStore((state) => state.setFleet);
@ -101,18 +110,58 @@ function App() {
setCenter(data); setCenter(data);
console.log(data, "AAEE"); console.log(data, "AAEE");
setIsNextButtonDisabled(false); setIsNextButtonDisabled(false);
setBuildings([]);
setHasFetchedBuildings(false);
}; };
const handleRemove = () => { const handleRemove = () => {
setAreaData([]); setAreaData([]);
setIsNextButtonDisabled(true); setIsNextButtonDisabled(true);
setBuildings([]);
setHasFetchedBuildings(false);
}; };
const handleClickNextStep = () => { const requestBuildings = async () => {
setIsFetchingBuildings(true);
const south = areaData[1].lat;
const west = areaData[1].lng;
const north = areaData[0].lat;
const east = areaData[0].lng;
const query = `[out:json][timeout:25];(way["building"]( ${south},${west},${north},${east} );relation["building"]( ${south},${west},${north},${east} ););out body geom;`;
try {
const response = await fetch("https://overpass-api.de/api/interpreter", {
method: "POST",
body: query,
headers: { "Content-Type": "application/x-www-form-urlencoded" },
});
const data = await response.json();
const blds: Building[] = data.elements.map((element) => ({
id: element.id,
tags: element.tags,
geometry: element.geometry
? element.geometry.map((pt) => ({ lat: pt.lat, lng: pt.lon }))
: undefined,
}));
setBuildings(blds);
appendAreas(blds);
setHasFetchedBuildings(true);
} catch (error) {
console.error("Error fetching building data:", error);
} finally {
setIsFetchingBuildings(false);
}
};
const handleClickNextStep = async () => {
if (step == 0 && checkIsBig()) { if (step == 0 && checkIsBig()) {
setIsWarnModal(true); setIsWarnModal(true);
return false; return false;
} }
if (step == 1 && !hasFetchedBuildings) {
await requestBuildings();
return;
}
setStep(step + 1); setStep(step + 1);
}; };
@ -153,10 +202,14 @@ function App() {
<Column gap="0.5rem"> <Column gap="0.5rem">
<Title>Processing</Title> <Title>Processing</Title>
<Description> <Description>
Click the button below to get the building information. Click Next Step to fetch building information. Once loaded, click
Next Step again to view the 3D scene.
</Description> </Description>
<BuildingHeights area={areaData} /> <BuildingHeights
buildings={buildings}
loading={isFetchingBuildings}
/>
</Column> </Column>
</Column> </Column>
</FullscreenModal> </FullscreenModal>
@ -167,10 +220,24 @@ function App() {
<NextButton <NextButton
isShow={step != 2} isShow={step != 2}
disabled={isNextButtonDisabled} disabled={isNextButtonDisabled || isFetchingBuildings}
onClick={handleClickNextStep} onClick={handleClickNextStep}
> >
Next Step <ChevronRight css={IconSize} /> {isFetchingBuildings ? (
<>
<Loader2
css={[
IconSize,
css({ animation: `${spinAnimation} 1s linear infinite` }),
]}
/>
Fetching...
</>
) : (
<>
Next Step <ChevronRight css={IconSize} />
</>
)}
</NextButton> </NextButton>
<NextButton isShow={step == 2} onClick={handleClickExport}> <NextButton isShow={step == 2} onClick={handleClickExport}>
@ -203,7 +270,7 @@ function App() {
GLB Download <Download css={IconSize} /> GLB Download <Download css={IconSize} />
</Button> </Button>
{isFleetLogin ? ( {/* {isFleetLogin ? (
<Button isShow={true} onClick={loadFleetSpace}> <Button isShow={true} onClick={loadFleetSpace}>
Fleet Interlock Fleet Interlock
</Button> </Button>
@ -214,7 +281,7 @@ function App() {
> >
Fleet Login Fleet Login
</Button> </Button>
)} )} */}
</Row> </Row>
</Column> </Column>
</Modal> </Modal>