Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
lgl
SmartVillages2
Commits
51717470
Commit
51717470
authored
Nov 26, 2025
by
Matthias Betz
Browse files
support geolocation
parent
a5904da0
Changes
4
Hide whitespace changes
Inline
Side-by-side
plugin-assets/IFC_Icon_J_v01.svg
→
src/icon.js
View file @
51717470
export
const
ifcIconSvg
=
`
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
...
...
@@ -6,14 +7,13 @@
version="1.1"
id="svg1"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
sodipodi:docname=
"IFC_Icon_J_v0
1
.svg"
sodipodi:docname="IFC_Icon_J_v0
2
.svg"
xml:space="preserve"
inkscape:export-filename=
"..\Export\IFC_Icon_
I
_v01.svg"
inkscape:export-filename="..\Export\IFC_Icon_
J_Plain
_v01.svg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
...
...
@@ -27,15 +27,15 @@
inkscape:document-units="px"
showborder="true"
showgrid="true"
inkscape:zoom=
"
8
"
inkscape:cx=
"
-39.25
"
inkscape:cy=
"13.
3125
"
inkscape:zoom="
22.627417
"
inkscape:cx="
10.827573
"
inkscape:cy="13.
50132
"
inkscape:window-width="1920"
inkscape:window-height="1129"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer=
"
g7
"
inkscape:current-layer="
layer5
"
showguides="false"><inkscape:grid
id="grid1"
units="px"
...
...
@@ -284,13 +284,11 @@
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;stroke-width:3.78418;enable-background:accumulate;stop-color:#000000"
d="m 16.719483,5.3400135 c 0,1.0219276 0.415528,1.9976456 1.152991,2.7050956 0.737462,0.7074485 1.571251,1.0679705 2.442959,1.0587195 l 0.395171,1.9863654 C 19.14539,11.155244 17.620858,10.57823 16.490363,9.493742 15.359868,8.4092541 14.71653,6.9065791 14.71653,5.3400135 Z"
id="use13-5"
sodipodi:nodetypes=
"csccscc"
/><use
x=
"0"
y=
"0"
xlink:href=
"#path10-4"
sodipodi:nodetypes="csccscc" /><path
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;stroke-width:3.78418;enable-background:accumulate;stop-color:#000000"
d="m 16.719483,1.6002563 c 0,1.0219286 0.415528,1.9976461 1.152991,2.7050959 0.737462,0.7074495 1.571251,1.0679715 2.44296,1.0587205 l 0.395171,1.9863596 C 19.14539,7.4154823 17.620858,6.8384683 16.490363,5.7539827 15.359868,4.6694977 14.71653,3.1668221 14.71653,1.6002563 Z"
id="use14-1"
transform=
"matrix(-1,0,0,1,29.433068,0)"
style=
"display:inline"
/></g><g
sodipodi:nodetypes="csccscc" /></g><g
inkscape:groupmode="layer"
id="g7"
inkscape:label="Haus1"
...
...
@@ -315,3 +313,6 @@
stroke-width="0"
id="path1"
style="display:inline;fill:#000000" /></g></svg>
`
;
export
default
ifcIconSvg
;
\ No newline at end of file
src/index.js
View file @
51717470
import
{
getPluginAssetUrl
,
NotificationType
,
ToolboxType
,
WindowSlot
}
from
'
@vcmap/ui
'
;
import
{
getPluginAssetUrl
,
NotificationType
,
ToolboxType
,
WindowSlot
,
}
from
'
@vcmap/ui
'
;
import
*
as
Cesium
from
'
@vcmap-cesium/engine
'
;
import
{
Cartesian3
,
...
...
@@ -15,6 +20,7 @@ import { mapVersion, name, version } from '../package.json';
import
BIMOptions
,
{
bimOptionsId
}
from
'
./upload.vue
'
;
import
objectList
,
{
loadCatalogModel
,
objectListId
}
from
'
./objectList.vue
'
;
import
scaleControl
,
{
scaleControlId
}
from
'
./scaleControl.vue
'
;
import
ifcIconSvg
from
'
@icon.js
'
;
let
model
;
let
conditions
=
[[
true
,
true
]];
...
...
@@ -29,38 +35,12 @@ let hpRoll;
let
app
;
let
currentlyScaledModel
;
const
hidingFeatures
=
[];
const
hidingListener
=
function
(
tile
)
{
let
allHidden
=
true
;
for
(
const
featureToHide
of
hidingFeatures
)
{
if
(
featureToHide
.
loops
<
10
)
{
allHidden
=
false
;
const
feature
=
tile
.
content
.
getFeature
(
featureToHide
.
id
);
if
(
feature
!==
undefined
)
{
app
.
maps
.
activeMap
.
layerCollection
.
globalHider
.
addFeature
(
'
manual
'
,
feature
,
);
app
.
maps
.
activeMap
.
layerCollection
.
globalHider
.
hideObjects
([
'
manual
'
]);
featureToHide
.
loops
++
;
}
}
}
if
(
allHidden
)
{
const
primitives
=
app
.
maps
.
activeMap
.
getScene
().
primitives
;
for
(
let
i
=
0
;
i
<
primitives
.
length
;
i
++
)
{
const
tileSet
=
primitives
.
get
(
i
);
if
(
!
(
tileSet
instanceof
Cesium3DTileset
))
{
continue
;
}
tileSet
.
tileVisible
.
removeEventListener
(
hidingListener
);
}
}
};
const
pluginState
=
{
files
:
[],
objects
:
[],
geolocations
:
{},
movedBy
:
{},
};
function
updateHideCondition
(
gmlId
)
{
...
...
@@ -118,6 +98,9 @@ function saveObjects() {
const
wgs84X
=
CMath
.
toDegrees
(
ellipPos
.
longitude
);
const
wgs84Y
=
CMath
.
toDegrees
(
ellipPos
.
latitude
);
const
height
=
ellipPos
.
height
;
if
(
m
.
model
.
fromCatalog
===
undefined
)
{
m
.
model
.
fromCatalog
=
false
;
}
outputArray
.
push
({
name
:
m
.
name
,
position
:
[
wgs84X
,
wgs84Y
,
height
],
...
...
@@ -148,20 +131,82 @@ function saveFile(fileName, urlFile) {
a
.
remove
();
}
async
function
placeModelAtPosition
(
vcMap
,
lon
,
lat
,
modelUrl
,
name
)
{
async
function
placeModelAtPosition
(
vcMap
,
lon
,
lat
,
modelUrl
,
name
,
rot
,
ele
)
{
// Create Cartographic position
const
position
=
Cesium
.
Cartographic
.
fromDegrees
(
lon
,
lat
);
// Wait for terrain height
const
[
updatedPosition
]
=
await
Cesium
.
sampleTerrainMostDetailed
(
vcMap
.
getCesiumWidget
().
terrainProvider
,
[
position
],
);
const
height
=
updatedPosition
.
height
+
10
;
console
.
log
(
"
Height:
"
+
height
);
console
.
log
(
"
Lon:
"
+
lon
);
console
.
log
(
"
Lat:
"
+
lat
);
const
cartesianPosition
=
Cartesian3
.
fromDegrees
(
lon
,
lat
,
height
);
console
.
log
(
cartesianPosition
);
let
height
=
ele
;
if
(
!
height
)
{
let
[
updatedPosition
]
=
await
Cesium
.
sampleTerrainMostDetailed
(
vcMap
.
getCesiumWidget
().
terrainProvider
,
[
position
],
);
height
=
updatedPosition
.
height
;
}
let
cartesianPosition
=
Cartesian3
.
fromDegrees
(
lon
,
lat
,
height
);
let
flyToPos
=
Cartesian3
.
fromDegrees
(
lon
,
lat
,
height
+
200
);
const
movedBy
=
pluginState
.
movedBy
[
name
];
if
(
movedBy
)
{
console
.
log
(
movedBy
);
// Old Cartesian position (Cesium.Cartesian3)
let
oldCartesian
=
cartesianPosition
;
// Build ENU transform at this point
let
enuTransform
=
Cesium
.
Transforms
.
eastNorthUpToFixedFrame
(
oldCartesian
);
// Local offset you want (meters)
let
localOffset
=
new
Cesium
.
Cartesian3
(
+
movedBy
.
x
,
-
movedBy
.
z
,
0
);
// Convert local ENU offset → ECEF movement
let
offsetMatrix
=
Cesium
.
Matrix4
.
multiplyByTranslation
(
enuTransform
,
localOffset
,
new
Cesium
.
Matrix4
(),
);
let
newCartesian
=
Cesium
.
Matrix4
.
getTranslation
(
offsetMatrix
,
new
Cesium
.
Cartesian3
());
let
newCartographic
=
Cesium
.
Cartographic
.
fromCartesian
(
newCartesian
);
if
(
!
ele
)
{
// update height for new location
// Wait for terrain height
let
[
updatedPosition
]
=
await
Cesium
.
sampleTerrainMostDetailed
(
vcMap
.
getCesiumWidget
().
terrainProvider
,
[
newCartographic
],
);
height
=
updatedPosition
.
height
;
}
let
newFlyPos
=
Cartesian3
.
fromRadians
(
newCartographic
.
longitude
,
newCartographic
.
latitude
,
height
+
200
,
);
newCartesian
=
Cartesian3
.
fromRadians
(
newCartographic
.
longitude
,
newCartographic
.
latitude
,
height
,
);
enuTransform
=
Cesium
.
Transforms
.
eastNorthUpToFixedFrame
(
newCartesian
);
// Local offset you want (meters)
localOffset
=
new
Cesium
.
Cartesian3
(
0
,
0
,
+
movedBy
.
y
);
// Convert local ENU offset → ECEF movement
offsetMatrix
=
Cesium
.
Matrix4
.
multiplyByTranslation
(
enuTransform
,
localOffset
,
new
Cesium
.
Matrix4
(),
);
newCartesian
=
Cesium
.
Matrix4
.
getTranslation
(
offsetMatrix
,
new
Cesium
.
Cartesian3
());
flyToPos
=
newFlyPos
;
cartesianPosition
=
newCartesian
;
}
// Add the model to the viewer
model
=
vcMap
.
getEntities
().
add
({
name
:
name
,
...
...
@@ -177,22 +222,33 @@ async function placeModelAtPosition(vcMap, lon, lat, modelUrl, name) {
model
:
model
,
name
:
name
,
});
if
(
rot
)
{
const
roll
=
new
HeadingPitchRoll
();
hpRollMap
.
set
(
model
,
roll
);
roll
.
heading
=
rot
;
if
(
roll
.
heading
>
CMath
.
TWO_PI
)
{
roll
.
heading
-=
CMath
.
TWO_PI
;
}
if
(
roll
.
heading
<
0.0
)
{
roll
.
heading
+=
CMath
.
TWO_PI
;
}
const
modelPos
=
model
.
position
.
getValue
();
model
.
orientation
=
new
ConstantProperty
(
Transforms
.
headingPitchRollQuaternion
(
modelPos
,
roll
),
);
}
vcMap
.
requestRender
();
vcMap
.
getCesiumWidget
().
camera
.
flyTo
({
destination
:
Cesium
.
Cartesian3
.
fromDegrees
(
lon
,
lat
,
1500
),
orientation
:
{
heading
:
Cesium
.
Math
.
toRadians
(
0.0
),
// rotation from north
pitch
:
Cesium
.
Math
.
toRadians
(
-
45.0
),
// negative to look down
roll
:
0.0
,
},
duration
:
2.0
,
// seconds
destination
:
flyToPos
,
});
}
function
setupModel
(
vcsApp
,
windowPos
,
url
,
name
,
fromCatalog
)
{
function
setupModel
(
vcsApp
,
windowPos
,
url
,
name
,
fromCatalog
,
useGeolocation
)
{
const
vcMap
=
vcsApp
.
maps
.
activeMap
;
const
geolocation
=
pluginState
.
geolocations
[
name
];
if
(
geolocation
===
undefined
)
{
if
(
!
useGeolocation
||
geolocation
===
undefined
)
{
setupModelWithoutGeolocation
(
vcMap
,
windowPos
,
...
...
@@ -205,7 +261,9 @@ function setupModel(vcsApp, windowPos, url, name, fromCatalog) {
}
else
{
const
lon
=
geolocation
.
lon
;
const
lat
=
geolocation
.
lat
;
placeModelAtPosition
(
vcMap
,
lon
,
lat
,
url
,
name
);
const
rot
=
geolocation
.
heading
;
const
ele
=
geolocation
.
ele
;
placeModelAtPosition
(
vcMap
,
lon
,
lat
,
url
,
name
,
rot
,
ele
);
}
}
...
...
@@ -473,7 +531,6 @@ export default function smartVillagesPlugin(config, baseUrl) {
loadingObject
.
position
[
1
],
loadingObject
.
position
[
2
],
);
console
.
log
(
cartPos
);
// Add the model to the viewer
model
=
vcMap
.
getEntities
().
add
({
name
:
loadingObject
.
name
,
...
...
@@ -667,10 +724,11 @@ export default function smartVillagesPlugin(config, baseUrl) {
* @returns {Promise<void>}
*/
async
onVcsAppMounted
(
vcsApp
)
{
let
importedIcon
=
`svgString:
${
ifcIconSvg
}
`
;
const
bimGroup
=
{
id
:
'
bim-functions
'
,
type
:
ToolboxType
.
GROUP
,
icon
:
getPluginAssetUrl
(
app
,
name
,
'
plugin-assets/IFC_Icon_J_v01.svg
'
)
,
icon
:
importedIcon
,
disabled
:
false
,
title
:
'
BIM- und Planungsfunktionen
'
,
};
...
...
src/objectList.vue
View file @
51717470
...
...
@@ -115,32 +115,49 @@ const items = ref(defaultItems);
let
setupModelFunction
;
let
app
;
let
projectStateObject
;
let
uploadedModels
=
[];
function
addModelToObjectList
(
name
,
url
)
{
function
addModelToObjectList
(
name
,
url
,
hasGeolocation
)
{
if
(
projectStateObject
===
undefined
)
{
// object library wasn't opened before
return
;
}
projectStateObject
.
objects
.
push
({
name
:
name
,
url
:
url
,
});
const
item
=
{
let
actions
=
[
{
name
:
'
Objekt platzieren
'
,
callback
()
{
const
x
=
window
.
innerWidth
/
2
;
const
y
=
window
.
innerHeight
/
2
;
const
cart
=
new
Cartesian2
(
x
,
y
);
setupModelFunction
(
app
,
cart
,
url
,
name
,
false
,
false
);
},
}
];
if
(
hasGeolocation
)
{
const
geolocation
=
projectStateObject
.
geolocations
[
name
];
const
lon
=
geolocation
.
lon
.
toFixed
(
1
);
const
lat
=
geolocation
.
lat
.
toFixed
(
1
);
actions
.
push
({
name
:
'
Objekt platzieren mit Georeferenz (
'
+
lon
+
'
°E
'
+
lat
+
'
°N)
'
,
callback
()
{
const
x
=
window
.
innerWidth
/
2
;
const
y
=
window
.
innerHeight
/
2
;
const
cart
=
new
Cartesian2
(
x
,
y
);
setupModelFunction
(
app
,
cart
,
url
,
name
,
false
,
true
);
},
});
}
const
placeModel
=
{
name
:
name
,
title
:
name
,
actions
:
[
{
name
:
'
Objekt platzieren
'
,
callback
()
{
const
x
=
window
.
innerWidth
/
2
;
const
y
=
window
.
innerHeight
/
2
;
const
cart
=
new
Cartesian2
(
x
,
y
);
setupModelFunction
(
app
,
cart
,
url
,
name
,
false
);
},
}
]
actions
:
actions
};
items
.
value
.
push
(
item
);
items
.
value
.
push
(
placeModel
);
}
async
function
loadCatalogsFromConfig
(
config
)
{
...
...
@@ -301,13 +318,28 @@ export async function loadCatalogModel(modelLink) {
})
}
export
function
loadModel
(
url
,
name
)
{
export
function
loadModelFromUrl
(
url
,
name
,
hasGeolocation
)
{
if
(
projectStateObject
===
undefined
)
{
// object library wasn't opened before
// store them, and load them once opened
uploadedModels
.
push
({
name
:
name
,
url
:
url
,
hasGeolocation
:
hasGeolocation
,
});
return
;
}
const
loader
=
new
GLTFLoader
();
loader
.
load
(
url
,
function
(
gltf
)
{
let
bounds
=
new
THREE
.
Box3
().
setFromObject
(
gltf
.
scene
);
const
x
=
bounds
.
min
.
x
+
(
bounds
.
max
.
x
-
bounds
.
min
.
x
)
/
2
;
const
z
=
bounds
.
min
.
z
+
(
bounds
.
max
.
z
-
bounds
.
min
.
z
)
/
2
;
const
m
=
new
THREE
.
Matrix4
().
makeTranslation
(
-
x
,
-
bounds
.
min
.
y
,
-
z
);
projectStateObject
.
movedBy
[
name
]
=
{
x
:
x
,
y
:
bounds
.
min
.
y
,
z
:
z
,
}
gltf
.
scene
.
applyMatrix4
(
m
);
const
exporter
=
new
GLTFExporter
();
const
options
=
{
...
...
@@ -319,7 +351,7 @@ export function loadModel(url, name) {
function
(
output
)
{
const
blob
=
new
Blob
([
output
]);
url
=
URL
.
createObjectURL
(
blob
);
addModelToObjectList
(
name
,
url
);
addModelToObjectList
(
name
,
url
,
hasGeolocation
);
},
// called when there is an error in the generation
function
(
error
)
{
...
...
@@ -364,7 +396,7 @@ function uploadModel() {
type
:
"
application/octet-stream
"
,
});
let
url
=
URL
.
createObjectURL
(
blob
);
loadModel
(
url
,
file
.
name
);
loadModel
FromUrl
(
url
,
file
.
name
,
false
);
}
reader
.
readAsArrayBuffer
(
file
);
}
...
...
@@ -434,7 +466,15 @@ export default {
const
dialog
=
ref
(
false
);
setupModelFunction
=
setupModel
;
projectStateObject
=
pluginState
;
onMounted
(()
=>
loadCatalogsFromConfig
(
config
).
then
(()
=>
{
console
.
log
(
"
Object library loaded
"
)
}));
onMounted
(()
=>
{
loadCatalogsFromConfig
(
config
).
then
(()
=>
{
console
.
log
(
"
Object library loaded
"
)
});
for
(
const
o
of
uploadedModels
)
{
loadModelFromUrl
(
o
.
url
,
o
.
name
,
o
.
hasGeolocation
);
}
uploadedModels
=
[];
});
return
{
draggable
,
selectable
,
...
...
@@ -548,15 +588,22 @@ export default {
display
:
grid
;
grid-template-columns
:
1fr
1fr
;
}
.no-scroll
{
max-height
:
none
;
/* remove height limit */
overflow
:
visible
;
/* show all content */
max-height
:
none
;
/* remove height limit */
overflow
:
visible
;
/* show all content */
padding-top
:
0
;
padding-bottom
:
0
;
}
.list-col
{
height
:
auto
;
/* let column grow with content */
min-height
:
0
;
/* reset flex min-height */
padding
:
0
;
/* remove top/bottom padding if necessary */
height
:
auto
;
/* let column grow with content */
min-height
:
0
;
/* reset flex min-height */
padding
:
0
;
/* remove top/bottom padding if necessary */
}
</
style
>
src/upload.vue
View file @
51717470
...
...
@@ -3,13 +3,13 @@
<v-card
class=
"pa-2 ma-2"
>
<v-container>
<v-row
no-gutters
class=
"justify-center"
>
<v-col>
<v-col
class=
"flex-grow-0"
>
<VcsLabel
html-for=
"fileInput"
>
IFC Datei:
</VcsLabel>
</v-col>
<v-col>
<Vcs
TextField
id=
"fileInput"
type=
"file"
@
change=
"onFileSelected"
/>
<Vcs
FileInput
id=
"fileInput"
type=
"file"
@
change=
"onFileSelected"
accept=
".ifc"
/>
</v-col>
</v-row>
<v-row
no-gutters
class=
"justify-center"
>
...
...
@@ -29,6 +29,7 @@ import {
VcsRadio
,
VcsFormButton
,
VcsTextField
,
VcsFileInput
,
NotificationType
,
VcsFormattedNumber
,
VcsFormSection
,
...
...
@@ -39,7 +40,7 @@ import {
import
{
VContainer
,
VRow
,
VForm
,
VCol
}
from
'
vuetify/components
'
;
import
{
name
}
from
'
../package.json
'
;
import
ConverterStatus
from
"
./converterStatus.vue
"
;
import
{
loadModel
}
from
"
./objectList.vue
"
;
import
{
loadModel
FromUrl
}
from
"
./objectList.vue
"
;
export
const
bimOptionsId
=
'
upload_ifc_id
'
;
...
...
@@ -55,6 +56,7 @@ export default {
VcsSelect
,
VcsTextField
,
VcsCheckbox
,
VcsFileInput
,
VcsRadio
,
VcsFormattedNumber
,
VcsFormSection
,
...
...
@@ -102,10 +104,18 @@ export default {
}
if
(
res
.
ok
)
{
// status code was 200-299
let
message
=
"
Datei erfolgreich konvertiert.
"
;
let
timeout
=
5000
;
if
(
geolocationString
)
{
const
geolocation
=
JSON
.
parse
(
geolocationString
);
message
=
"
Datei erfolgreich konvertiert. Gefundene Position: (
"
+
geolocation
.
lon
+
"
°E
"
+
geolocation
.
lat
+
"
°N)
"
;
timeout
=
15000
;
}
app
.
notifier
.
add
({
type
:
NotificationType
.
SUCCESS
,
message
:
"
Datei erfolgreich konvertiert
"
,
timeout
:
5000
,
message
:
message
,
timeout
:
timeout
,
});
return
res
.
blob
();
}
else
{
...
...
@@ -120,6 +130,7 @@ export default {
type
:
NotificationType
.
ERROR
,
message
:
errorMessage
,
});
disable
.
value
=
false
;
}).
then
((
blob
)
=>
{
if
(
blob
!=
null
)
{
var
url
=
window
.
URL
.
createObjectURL
(
blob
);
...
...
@@ -129,7 +140,11 @@ export default {
document
.
body
.
appendChild
(
a
);
a
.
click
();
a
.
remove
();
loadModel
(
url
,
filename
);
let
hasGeolocation
=
pluginState
.
geolocations
[
filename
]
!=
undefined
;
if
(
!
hasGeolocation
)
{
hasGeolocation
=
false
;
}
loadModelFromUrl
(
url
,
filename
,
hasGeolocation
);
}
})
},
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment