├── Duplex_A_20110907.ifc
├── Duplex_A_20110907_georeferenced.ifc
├── LICENSE
├── README.md
├── apt.txt
├── environment.yml
├── georeference_ifc
├── IFC2X3_Geolocation.ifc
├── __init__.py
└── main.py
├── how-to-georeference-ifc-file.ipynb
├── postBuild
├── tests
└── test_georeference_ifc.py
└── util.py
/LICENSE:
--------------------------------------------------------------------------------
1 | Creative Commons Attribution 4.0 International Public License (CC BY 4.0)
2 |
3 | Copyright (c) 2021 Stijn Goedertier
4 |
5 | https://creativecommons.org/licenses/by/4.0/
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # How to georeference an IFC file
2 |
3 | [](https://mybinder.org/v2/gh/stijngoedertier/georeference-ifc/master?labpath=how-to-georeference-ifc-file.ipynb)
4 |
5 | Run this notebook on [mybinder.org](](https://mybinder.org/v2/gh/stijngoedertier/georeference-ifc/master?labpath=how-to-georeference-ifc-file.ipynb).
6 |
7 | # How to georeference a BIM model
8 |
9 | IFC is an open standard for exchanging Building Information Models (BIM).
10 | This notebook shows how IFC files can be georeferenced by providing **7 parameters** that are part of the IFC standard but are usually omitted.
11 | These parameters make it easier to **combine BIM and geospatial data** (e.g. cadastral parcel data, 3D city models, point cloud data, or a digital elevation model) and enable use cases such as urban planning, evaluation of building permits or asset management.
12 |
13 | ## The problem
14 | One of the impediments to the automated processing of IFC data is the fact that georeferencing information is not *explicitly* included in IFC information exchanges. Two scenarios are possible:
15 | 1. **Local reference system**: in most construction projects, the partners share a common *local* reference system, which is defined as a local origin and axis orientation. The local origin is often a conspicuous point near the construction site and the axis orientation is often chosen for convenience (e.g. along the axis of the main construction). Although the geolocation of this local reference system is known to the partners, there is usually no information about it in the IFC file.
16 | 1. **Geospatial reference system**: In other construction projects, the partners exchange IFC files in a standard projected reference system used by land surveyors or geospatial data (for example [EPSG:2169](https://epsg.io/2169) Luxembourg). Although this information is known to the partners in the project, it is often left implicit in the IFC file thus inhibiting the possibility for automated processing.
17 |
18 | ## The solution
19 | In both scenarios, the georeferencing information can be made explicit by using the **IfcProjectedCRS** and **IfcMapConversion** entities from the [IFC4 standard](https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/templates/project-global-positioning.htm). The former allows specifying a recognized, target coordinate reference system (CRS). The latter can be used to specify the coordinate transformation needed to convert coordinates from the project CRS to the specified target CRS. As depicted in the diagram below from the IFC standard, these ifc entities are directly related to the IfcProject (a subclass of IfcContext) entity.
20 |
21 | [](https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/templates/project-global-positioning.htm)
22 |
23 | [IfcProjectedCRS](https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcrepresentationresource/lexical/ifcprojectedcrs.htm) allows specifying a target projected coordinate reference system. The most relevant attribute is the name of the reference system:
24 | * **IfcProjectedCRS.Name**: According to the IFC4 specification, the name shall be taken from the list recognized by the European Petroleum Survey Group EPSG and should be qualified by the EPSG name space, for example as '[EPSG:2169](https://epsg.io/2169)'. In case the EPSG registry includes a vertical datum for the coordinate reference system - as is the case for our example - no other properties need to be provided.
25 |
26 | [IfcMapConversion](https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcrepresentationresource/lexical/ifcmapconversion.htm) provides information on how the reference system of the IFC file can be converted into the reference system indicated by IfcProjectedCRS. It contains information on the required coordinate translation, rotation, and scaling:
27 | * **IfcMapConversion.Eastings, IfcMapConversion.Northings, IfcMapConversion.OrthogonalHeight** indicate the position of the origin of the local reference system used in the IFC file in terms of the indicated IfcProjectedCRS. Together, they define the coordinate *translation* needed to convert the *origin* of the local project CRS into the origin of the target geospatial CRS.
28 | * **IfcMapConversion. XAxisAbscissa and IfcMapConversion.XAxisOrdinate** together define the angle of *rotation* required to convert the axis orientation of the local reference system, into the axis orientation of the indicated coordinate reference system.
29 | * **IfcMapConversion.Scale** indicates the conversion factor to be used, to convert the units of the local, engineering coordinate system into the units of the target CRS (often expressed in metres). If omitted, the value of 1.0 is assumed.
30 |
31 | In IFC2x3 the same information can be included by using equivalent property sets 'ePset_MapConversion' and 'ePSet_ProjectedCRS' on IfcSite as proposed by BuildingSMART Australasia in their paper '[User guide for georeferencing in IFC](https://www.buildingsmart.org/wp-content/uploads/2020/02/User-Guide-for-Geo-referencing-in-IFC-v2.0.pdf)'.
32 |
33 | Recent versions of [Revit](https://wiki.osarch.org/index.php?title=Revit_IFC_geolocation) (with the [revit-ifc plugin](https://github.com/Autodesk/revit-ifc)) and [ArchiCAD](https://wiki.osarch.org/index.php?title=ArchiCAD_IFC_geolocation) make it possible export this IFC information. See the osarch.org wiki or the [blog post](https://thinkmoult.com/ifc-coordinate-reference-systems-and-revit.html) by Dion Moult for a detailed guide on georeferencing in Revit.
34 |
35 | ## Add georeference information with IfcOpenShell-python
36 |
37 | [IfcOpenShell-python](https://github.com/IfcOpenShell/IfcOpenShell) is an open-source library for working with IFC files. Let's use [IfcOpenShell-python](https://github.com/IfcOpenShell/IfcOpenShell) to write or read georeferencing information from an IFC file.
38 | For this purpose, we have taken the [Duplex Apartment](https://github.com/buildingSMART/Sample-Test-Files/tree/master/IFC%202x3/Duplex%20Apartment) IFC file by BuildingSMART International.
39 |
40 | ### Visualize the spaces in the IFC file
41 |
42 | First, let's visualize the rooms (IfcSpace) in the IFC file using a threeJS viewer. This filter on IfcSpace objects is relevant because we usually do not need all the detail in the IFC file when combining it with GIS data. The viewer shows the XYZ axis and origin of the local reference system. Its origin is located very near the construction site.
43 |
44 |
45 |
46 |
47 | ```python
48 | from math import atan2, degrees
49 | import ifcopenshell
50 | import ifcopenshell.geom
51 | from OCC.Display.WebGl.jupyter_renderer import JupyterRenderer, format_color
52 |
53 | settings = ifcopenshell.geom.settings()
54 | settings.set(settings.USE_WORLD_COORDS, True)
55 | settings.set(settings.USE_BREP_DATA, True)
56 | settings.set(settings.USE_PYTHON_OPENCASCADE, True)
57 |
58 | fn = './Duplex_A_20110907.ifc'
59 | ifc_file = ifcopenshell.open(fn)
60 |
61 | threejs_renderer = JupyterRenderer(size=(500, 500))
62 | spaces = ifc_file.by_type("IfcSpace")
63 |
64 | for space in spaces:
65 | if space.Representation is not None:
66 | shape = ifcopenshell.geom.create_shape(settings, inst=space)
67 | r,g,b,alpha = shape.styles[0]
68 | color = format_color(int(abs(r)*255), int(abs(g)*255), int(abs(b)*255))
69 | threejs_renderer.DisplayShape(shape.geometry, shape_color = color, transparency=True, opacity=alpha, render_edges=True)
70 |
71 | threejs_renderer.Display()
72 | ```
73 |
74 | ### Write georeference parameters
75 | Now, let's use IfcOpenShell-python to add geoloation information to the IFC file with the function `set_mapconversion_crs()` in [georeference_ifc/main.py](georeference_ifc/main.py).
76 |
77 | ```python
78 | import georeference_ifc
79 |
80 | georeference_ifc.set_mapconversion_crs(ifc_file=ifc_file,
81 | target_crs_epsg_code='EPSG:2169',
82 | eastings=76670.0,
83 | northings=77179.0,
84 | orthogonal_height=293.700012207031,
85 | x_axis_abscissa=0.325568154457152,
86 | x_axis_ordinate=0.945518575599318,
87 | scale=1.0)
88 |
89 | ```
90 |
91 |
92 | ### Read georeference information
93 |
94 | The function `get_mapconversion_crs()` in [georeference_ifc/main.py](georeference_ifc/main.py) can be used to extract georeferencing information from an IFC file. From XAxisAbscissa and XAxisOrdinate we calculate the rotation.
95 |
96 | ```python
97 | import georeference_ifc
98 |
99 | IfcMapConversion, IfcProjectedCRS = georeference_ifc.get_mapconversion_crs(ifc_file=ifc_file)
100 |
101 |
102 | import pandas as pd
103 | from IPython.display import display
104 | df = pd.DataFrame(list(IfcProjectedCRS.__dict__.items()) + list(IfcMapConversion.__dict__.items()), columns= ['property', 'value'])
105 | display(df)
106 |
107 | rotation = degrees(atan2(IfcMapConversion.XAxisOrdinate, IfcMapConversion.XAxisAbscissa))
108 | print(f'Rotation is: {rotation:.1f}° (degrees(atan2(map_conversion.XAxisOrdinate, map_conversion.XAxisAbscissa)) ')
109 | ```
110 |
111 |
112 |
113 |
126 |
127 |
128 |
129 | |
130 | property |
131 | value |
132 |
133 |
134 |
135 |
136 | 0 |
137 | Name |
138 | EPSG:2169 |
139 |
140 |
141 | 1 |
142 | Eastings |
143 | 76670.0 |
144 |
145 |
146 | 2 |
147 | Northings |
148 | 77179.0 |
149 |
150 |
151 | 3 |
152 | OrthogonalHeight |
153 | 293.700012 |
154 |
155 |
156 | 4 |
157 | XAxisAbscissa |
158 | 0.325568 |
159 |
160 |
161 | 5 |
162 | XAxisOrdinate |
163 | 0.945519 |
164 |
165 |
166 | 6 |
167 | Scale |
168 | 1.0 |
169 |
170 |
171 |
172 |
173 |
174 |
175 | Rotation is: 71.0° (degrees(atan2(map_conversion.XAxisOrdinate, map_conversion.XAxisAbscissa))
176 |
177 |
178 | ### Convert 3D spaces into 2D polygons (footprint)
179 |
180 | We will use a utility function `shape_to_polygons(shape)` that will convert a 3D space into a 2D polygon, by extracting the shape into faces. Only the first face that is converted into a valid polygon is taken. We then use [GeoPandas](https://geopandas.org) to convert the polygons from the project CRS into the geospatial CRS using the IfcMapConversion parameters. We will respectively apply a rotation, translation, and scaling.
181 |
182 |
183 | ```python
184 | from util import shape_to_polygons
185 | import geopandas as gpd
186 | from geopandas import GeoSeries
187 |
188 | polygons = []
189 | names = []
190 | for space in spaces:
191 | if space.Representation is not None:
192 | shape = ifcopenshell.geom.create_shape(settings, inst=space)
193 | shape_polygons = shape_to_polygons(shape)
194 | polygons = polygons + shape_polygons
195 | names = names + [space.Name for _ in shape_polygons]
196 |
197 |
198 | footprint = GeoSeries(polygons,crs=IfcProjectedCRS.Name)
199 | footprint = footprint\
200 | .rotate(rotation, origin=(0,0,), use_radians=False)\
201 | .translate(IfcMapConversion.Eastings, IfcMapConversion.Northings, 0)\
202 | .scale(IfcMapConversion.Scale if IfcMapConversion.Scale else 1.0)
203 | ```
204 |
205 | ### Add footprints to map
206 | The resulting building footprint data is pure geospatial data that can be depicted on a map and combined with other geospatial data, in this example with [OpenStreetMap](https://www.openstreetmap.org) and [geoportail.lu](https://map.geoportail.lu/) data.
207 |
208 |
209 | ```python
210 | import folium
211 | import json
212 | import requests
213 |
214 | m = folium.Map(location=[49.629, 6.122], zoom_start=20, tiles='CartoDB positron', max_zoom=30)
215 |
216 | url="https://wms.inspire.geoportail.lu/geoserver/cp/wfs?service=WFS&request=GetFeature&version=2.0.0&srsName=urn:ogc:def:crs:EPSG::3857&typeNames=cp%3ACP.CadastralParcel&bbox=681342.6715153919,6382237.960593072,681731.4043978148,6382399.0656238245,urn:ogc:def:crs:EPSG::3857&namespaces=xmlns(cp,http%3A%2F%2Fcp)&count=2000&outputFormat=json"
217 | df_cp = gpd.read_file(requests.get(url).text, crs='EPSG:3857')
218 | df_cp = df_cp.to_crs(epsg=4326)
219 | folium.GeoJson(df_cp.to_json(), name="cadastral parcels").add_to(m)
220 |
221 | df = gpd.GeoDataFrame({'name': names}, geometry=footprint)
222 | df = df.to_crs(epsg=4326)
223 | for _, r in df.iterrows():
224 | sim_geo = gpd.GeoSeries(r['geometry'])
225 | geo_j = sim_geo.to_json()
226 | geo_j = folium.GeoJson(data=geo_j,
227 | style_function=lambda x: {'fillColor': 'orange'})
228 | folium.Popup(r['name']).add_to(geo_j)
229 | geo_j.add_to(m)
230 |
231 | m
232 | ```
233 |
234 | ## Conclusion
235 |
236 | The use of **IfcProjectedCRS** and **IfcMapConversion** is a precise and unitrusive way of adding geolocation information to an IFC file.
237 | * This solution does not require you to change the local reference system, only to provide information about it.
238 | * It is more accurate compared the use of IfcSite.RefLongitude and IfcSite.RefLatitude in [WGS84](https://epsg.io/4326) coordinates, as a more appropriate geospatial coordinate reference system can be used.
239 | * BIM tools such as Revit and ArchiCAD already support it.
240 | * If your BIM tool does not support it, you can use IfcOpenShell to postprocess your IFC prior to exchanging it with third parties.
241 |
242 | Therefore providing this information should considered to be made mandatory when drafting BIM information exchange agreements.
243 |
244 | ## References
245 |
246 | BuildingSMART. IFC Specifications database. https://technical.buildingsmart.org/standards/ifc/ifc-schema-specifications/
247 |
248 | BuildingSMART Australasia (2020). User Guide for Geo-referencing in IFC, version 2.0. https://www.buildingsmart.org/wp-content/uploads/2020/02/User-Guide-for-Geo-referencing-in-IFC-v2.0.pdf
249 |
250 | Dion Moult (2019). IFC Coordinate Reference Systems and Revit. https://thinkmoult.com/ifc-coordinate-reference-systems-and-revit.html
251 |
252 | OSarch.org (2021). IFC2x3 geo-referencing property set template definitions. https://wiki.osarch.org/index.php?title=IFC_geolocation
253 |
254 | Clemen, C., & Görne, H. (2019). Level of Georeferencing (LoGeoRef) using IFC for BIM. Journal of Geodesy, Cartography and Cadastre, (10). https://jgcc.geoprevi.ro/docs/2019/10/jgcc_2019_no10_3.pdf
255 |
256 | Noardo, F., Krijnen, T., Arroyo Ohori, K., Biljecki, F., Ellul, C., Harrie, L., Eriksson, H., Polia, L., Salheb, N., Tauscher, H., van Liempt, J., Goerne, H., Hintz, D., Kaiser, T., Leoni, C., Warchol, A., Stoter, J. (2021). Reference study of IFC software support: the GeoBIM benchmark 2019 – Part I. Transactions in GIS.
257 |
258 |
259 | ## Comments
260 | Comments are welcome via GitHub [issues](https://github.com/stijngoedertier/georeference-ifc).
261 |
262 |
263 | ## License
264 |
265 | This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International [(CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/) License.
--------------------------------------------------------------------------------
/apt.txt:
--------------------------------------------------------------------------------
1 | libgl1-mesa-glx
2 |
--------------------------------------------------------------------------------
/environment.yml:
--------------------------------------------------------------------------------
1 | name: base
2 | channels:
3 | - conda-forge
4 | dependencies:
5 | - python
6 | - jupyterlab
7 | - nodejs
8 | - ipywidgets
9 | - widgetsnbextension
10 | - pythonocc-core
11 | - pythreejs
12 | - ifcopenshell
13 | - python-graphviz
14 | - pandas
15 | - geopandas
16 | - shapely
17 | - folium
18 |
19 |
--------------------------------------------------------------------------------
/georeference_ifc/IFC2X3_Geolocation.ifc:
--------------------------------------------------------------------------------
1 | ISO-10303-21;
2 | HEADER;
3 | FILE_DESCRIPTION((),'2;1');
4 | FILE_NAME('IFC2X3_Geolocation.ifc','2020-01-01T00:00:00',(),(),'Sample','Sample',$);
5 | FILE_SCHEMA(('IFC4'));
6 | ENDSEC;
7 | DATA;
8 | #1=IFCPROPERTYSETTEMPLATE('3YdVbPbUXA$QcmIvcLKxCg',$,'EPset_ProjectedCRS','An IFC2x3 convention for georeferencing',.PSET_OCCURRENCEDRIVEN.,'IfcSite',(#2,#3,#4,#5,#6,#7,#8));
9 | #2=IFCSIMPLEPROPERTYTEMPLATE('2lLZAU4LT7phTIqtaQRX9J',$,'Name','Name by which the coordinate reference system is identified. ',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
10 | #3=IFCSIMPLEPROPERTYTEMPLATE('1KDvMgh29EbQRikH8_h4FH',$,'Description','Informal description of this coordinate reference system. ',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
11 | #4=IFCSIMPLEPROPERTYTEMPLATE('3lDBscbyLFyvE4xcvYA7Bb',$,'GeodeticDatum','Name by which this datum is identified. The geodetic datum is associated with the coordinate reference system and indicates the shape and size of the rotation ellipsoid and this ellipsoid''s connection and orientation to the actual globe/earth. It needs to be provided, if the Name identifier does not unambiguously define the geodetic datum as well. ',.P_SINGLEVALUE.,'IfcIdentifier',$,$,$,$,$,.READWRITE.);
12 | #5=IFCSIMPLEPROPERTYTEMPLATE('1m3ZjUUVf3tfHK4GD0D$If',$,'VerticalDatum','Name by which the vertical datum is identified. The vertical datum is associated with the height axis of the coordinate reference system and indicates the reference plane and fundamental point defining the origin of a height system. It needs to be provided, if the Name identifier does not unambiguously define the vertical datum as well and if the coordinate reference system is a 3D reference system. ',.P_SINGLEVALUE.,'IfcIdentifier',$,$,$,$,$,.READWRITE.);
13 | #6=IFCSIMPLEPROPERTYTEMPLATE('3sUHnVnqXElRFuVaLpQj_2',$,'MapProjection','Name by which the map projection is identified. ',.P_SINGLEVALUE.,'IfcIdentifier',$,$,$,$,$,.READWRITE.);
14 | #7=IFCSIMPLEPROPERTYTEMPLATE('2uW2Bus$H6bAn9liUM2ui7',$,'MapZone','Name by which the map zone, relating to the MapProjection, is identified. ',.P_SINGLEVALUE.,'IfcIdentifier',$,$,$,$,$,.READWRITE.);
15 | #8=IFCSIMPLEPROPERTYTEMPLATE('0OrG0a0MTCtOrw0$jtMVW3',$,'MapUnit','Unit of the coordinate axes composing the map coordinate system.',.P_SINGLEVALUE.,'IfcIdentifier',$,$,$,$,$,.READWRITE.);
16 | #9=IFCPROPERTYSETTEMPLATE('0TnwG3fwj8RPXQUzCt31Ti',$,'EPset_MapConversion','An IFC2x3 convention for georeferencing',.PSET_OCCURRENCEDRIVEN.,'IfcSite',(#10,#11,#12,#13,#14,#15));
17 | #10=IFCSIMPLEPROPERTYTEMPLATE('1UJp$3bALBShK9BBCAmy85',$,'Eastings','Specifies the location along the easting of the coordinate system of the target map coordinate reference system. ',.P_SINGLEVALUE.,'IfcLengthMeasure',$,$,$,$,$,.READWRITE.);
18 | #11=IFCSIMPLEPROPERTYTEMPLATE('3VANBAqv90G8cCyRQ0rFs_',$,'Northings','Specifies the location along the northing of the coordinate system of the target map coordinate reference system. ',.P_SINGLEVALUE.,'IfcLengthMeasure',$,$,$,$,$,.READWRITE.);
19 | #12=IFCSIMPLEPROPERTYTEMPLATE('3W2BUJsh57ZRjcFaF6HDRB',$,'OrthogonalHeight','Orthogonal height relativ to the vertical datum specified. ',.P_SINGLEVALUE.,'IfcLengthMeasure',$,$,$,$,$,.READWRITE.);
20 | #13=IFCSIMPLEPROPERTYTEMPLATE('3SThS3spnEQBfGxK6$FSoS',$,'XAxisAbscissa','Specifies the value along the easing axis of the end point of a vector indicating the position of the local x axis of the engineering coordinate reference system. ',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
21 | #14=IFCSIMPLEPROPERTYTEMPLATE('1rwwnE10XA5eME0lfYSR4X',$,'XAxisOrdinate','Specifies the value along the northing axis of the end point of a vector indicating the position of the local x axis of the engineering coordinate reference system. ',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
22 | #15=IFCSIMPLEPROPERTYTEMPLATE('2GXSPBbN133egiadM02BGW',$,'Scale','Scale to be used, when the units of the CRS are not identical to the units of the engineering coordinate system. If omited, the value of 1.0 is assumed. ',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
23 | ENDSEC;
24 | END-ISO-10303-21;
25 |
--------------------------------------------------------------------------------
/georeference_ifc/__init__.py:
--------------------------------------------------------------------------------
1 | from .main import set_mapconversion_crs, set_si_units, get_mapconversion_crs, get_rotation
2 |
--------------------------------------------------------------------------------
/georeference_ifc/main.py:
--------------------------------------------------------------------------------
1 | import os
2 | import ifcopenshell
3 | import ifcopenshell.util.unit
4 | import ifcopenshell.api
5 | import ifcopenshell.util.element
6 | import ifcopenshell.util.pset
7 | from ifcopenshell.util.element import get_psets
8 | import math
9 |
10 |
11 | def set_mapconversion_crs(ifc_file: ifcopenshell.file,
12 | target_crs_epsg_code: str,
13 | eastings: float,
14 | northings: float,
15 | orthogonal_height: float,
16 | x_axis_abscissa: float,
17 | x_axis_ordinate: float,
18 | scale: float) -> None:
19 | """
20 | This method adds IFC map conversion information to an IfcOpenShell file.
21 | IFC map conversion information indicates how the local coordinate reference system of the IFC file
22 | can be converted into a global coordinate reference system. The latter is the IfcProjectedCRS.
23 |
24 | The method detects whether the schema of the IFC file is IFC2X3 or IFC4.
25 | In case of IFC4, an IfcMapConversion and IfcProjectedCRS are created and associated with IfcProject.
26 | For IFC2X3, corresponding property sets are created and associated with IfcSite.
27 |
28 | :param ifc_file: ifcopenshell.file
29 | the IfcOpenShell file object in which IfcMapConversion information is inserted.
30 | :param target_crs_epsg_code: str
31 | the EPSG-code of the target coordinate reference system formatted as a string (e.g. EPSG:2169).
32 | According to the IFC specification, only a projected coordinate reference system
33 | with cartesian coordinates can be used.
34 | :param eastings: float
35 | the coordinate shift (translation) that is added to an x-coordinate to convert it
36 | into the Eastings of the target reference system.
37 | :param northings: float
38 | the coordinate shift (translation) that is added to an y-coordinate to convert it
39 | into the Northings of the target reference system.
40 | :param orthogonal_height: float
41 | the coordinate shift (translation) that is applied to a z-coordinate to convert it
42 | into a height relative to the vertical datum of the target reference system.
43 | :param x_axis_abscissa: float
44 | defines a rotation (together with x_axis_ordinate) around the z-axis of the local coordinate system
45 | to orient it according to the target reference system.
46 | x_axis_abscissa is the component of a unit vector along the x-axis of the local reference system projected
47 | on the Eastings axis of the target reference system.
48 | :param x_axis_ordinate: float
49 | defines a rotation (together with x_axis_abscissa) around the z-axis of the local coordinate system
50 | to orient it according to the target reference system.
51 | x_axis_abscissa is the component of a unit vector along the x-axis of the local reference system projected
52 | on the Northings axis of the target reference system.
53 | :param scale: float, optional
54 | indicates the conversion factor to be used, to convert the units of the local coordinate
55 | system into the units of the target CRS (often expressed in metres).
56 | If omitted, a value of 1.0 is assumed.
57 | """
58 | if ifc_file.schema == 'IFC4':
59 | set_mapconversion_crs_ifc4(ifc_file, target_crs_epsg_code, eastings, northings, orthogonal_height,
60 | x_axis_abscissa,
61 | x_axis_ordinate, scale)
62 | if ifc_file.schema == 'IFC2X3':
63 | set_mapconversion_crs_ifc2x3(ifc_file, target_crs_epsg_code, eastings, northings, orthogonal_height,
64 | x_axis_abscissa, x_axis_ordinate, scale)
65 |
66 |
67 | def set_si_units(ifc_file: ifcopenshell.file):
68 | """
69 | This method adds standardized units to an IFC file.
70 |
71 | :param ifc_file:
72 | """
73 | lengthunit = ifcopenshell.api.run("unit.add_si_unit", ifc_file, unit_type="LENGTHUNIT", name="METRE", prefix=None)
74 | areaunit = ifcopenshell.api.run("unit.add_si_unit", ifc_file, unit_type="AREAUNIT", name="SQUARE_METRE",
75 | prefix=None)
76 | volumeunit = ifcopenshell.api.run("unit.add_si_unit", ifc_file, unit_type="VOLUMEUNIT", name="CUBIC_METRE",
77 | prefix=None)
78 | ifcopenshell.api.run("unit.assign_unit", ifc_file, units=[lengthunit, areaunit, volumeunit])
79 |
80 |
81 | def set_mapconversion_crs_ifc4(ifc_file: ifcopenshell.file,
82 | target_crs_epsg_code: str,
83 | eastings: float,
84 | northings: float,
85 | orthogonal_height: float,
86 | x_axis_abscissa: float,
87 | x_axis_ordinate: float,
88 | scale: float) -> None:
89 | # we assume that the IFC file only has one IfcProject entity.
90 | source_crs = ifc_file.by_type('IfcProject')[0].RepresentationContexts[0]
91 | target_crs = ifc_file.createIfcProjectedCRS(
92 | Name=target_crs_epsg_code
93 | )
94 | ifc_file.createIfcMapConversion(
95 | SourceCRS=source_crs,
96 | TargetCRS=target_crs,
97 | Eastings=eastings,
98 | Northings=northings,
99 | OrthogonalHeight=orthogonal_height,
100 | XAxisAbscissa=x_axis_abscissa,
101 | XAxisOrdinate=x_axis_ordinate,
102 | Scale=scale
103 | )
104 |
105 |
106 | def set_mapconversion_crs_ifc2x3(ifc_file: ifcopenshell.file,
107 | target_crs_epsg_code: str,
108 | eastings: float,
109 | northings: float,
110 | orthogonal_height: float,
111 | x_axis_abscissa: float,
112 | x_axis_ordinate: float,
113 | scale: float) -> None:
114 | # Open the IFC property set template provided by OSarch.org on https://wiki.osarch.org/index.php?title=File:IFC2X3_Geolocation.ifc
115 | ifc_template = ifcopenshell.open(os.path.join(os.path.dirname(__file__), './IFC2X3_Geolocation.ifc'))
116 | map_conversion_template = \
117 | [t for t in ifc_template.by_type('IfcPropertySetTemplate') if t.Name == 'EPset_MapConversion'][0]
118 | crs_template = [t for t in ifc_template.by_type('IfcPropertySetTemplate') if t.Name == 'EPset_MapConversion'][0]
119 |
120 | site = ifc_file.by_type("IfcSite")[0] # we assume that the IfcProject only has one IfcSite entity.
121 | pset = ifcopenshell.api.run("pset.add_pset", ifc_file, product=site, name="ePSet_MapConversion")
122 | ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties={'Eastings': eastings,
123 | 'Northings': northings,
124 | 'OrthogonalHeight': orthogonal_height,
125 | 'XAxisAbscissa': x_axis_abscissa,
126 | 'XAxisOrdinate': x_axis_ordinate,
127 | 'Scale': scale},
128 | pset_template=map_conversion_template)
129 | pset = ifcopenshell.api.run("pset.add_pset", ifc_file, product=site, name="ePSet_ProjectedCRS")
130 | ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties={'Name': target_crs_epsg_code},
131 | pset_template=crs_template)
132 |
133 |
134 | def get_mapconversion_crs(ifc_file: ifcopenshell.file) -> (object, object):
135 | """
136 | This method returns a tuple (IfcMapConversion, IfcProjectedCRS) from an IfcOpenShell file object.
137 |
138 | :param ifc_file: ifcopenshell.file
139 | the IfcOpenShell file object from which georeference information is read.
140 | :returns a tuple of two objects IfcMapConversion, IfcProjectedCRS.
141 | """
142 |
143 | class Struct:
144 | def __init__(self, **entries):
145 | self.__dict__.update(entries)
146 |
147 | mapconversion = None
148 | crs = None
149 |
150 | if ifc_file.schema == 'IFC4':
151 | project = ifc_file.by_type("IfcProject")[0]
152 | for c in (m for c in project.RepresentationContexts for m in c.HasCoordinateOperation):
153 | return c, c.TargetCRS
154 | if ifc_file.schema == 'IFC2X3':
155 | site = ifc_file.by_type("IfcSite")[0]
156 | psets = get_psets(site)
157 | if 'ePSet_MapConversion' in psets.keys() and 'ePSet_ProjectedCRS' in psets.keys():
158 | return Struct(**psets['ePSet_MapConversion']), Struct(**psets['ePSet_ProjectedCRS'])
159 | return mapconversion, crs
160 |
161 |
162 | def get_rotation(mapconversion) -> float:
163 | """
164 | This method calculates the rotation (in degrees) for a given mapconversion data structure,
165 | from its XAxisAbscissa and XAxisOrdinate properties.
166 |
167 | :returns the rotation in degrees along the Z-axis (the axis orthogonal to the earth's surface). For a right-handed
168 | coordinate reference system (as required) a postitive rotation angle implies a counter-clockwise rotation.
169 | """
170 | return math.degrees(math.atan2(mapconversion.XAxisOrdinate, mapconversion.XAxisAbscissa))
171 |
--------------------------------------------------------------------------------
/how-to-georeference-ifc-file.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "97105b44",
6 | "metadata": {},
7 | "source": [
8 | "# How to georeference a BIM model\n",
9 | "\n",
10 | "IFC is an open standard for exchanging Building Information Models (BIM).\n",
11 | "This notebook shows how IFC files can be georeferenced by providing **7 parameters** that are part of the IFC standard but are usually omitted.\n",
12 | "These parameters make it easier to **combine BIM and geospatial data** (e.g. cadastral parcel data, 3D city models, point cloud data, or a digital elevation model) and enable use cases such as urban planning, evaluation of building permits or asset management.\n",
13 | "\n",
14 | "## The problem\n",
15 | "One of the impediments to the automated processing of IFC data is the fact that georeferencing information is not *explicitly* included in IFC information exchanges. Two scenarios are possible:\n",
16 | " 1. **Local reference system**: in most construction projects, the partners share a common *local* reference system, which is defined as a local origin and axis orientation. The local origin is often a conspicuous point near the construction site and the axis orientation is often chosen for convenience (e.g. along the axis of the main construction). Although the geolocation of this local reference system is known to the partners, there is usually no information about it in the IFC file.\n",
17 | " 1. **Geospatial reference system**: In other construction projects, the partners exchange IFC files in a standard projected reference system used by land surveyors or geospatial data (for example [EPSG:9897](https://epsg.io/9897) Luxembourg). Although this information is known to the partners in the project, it is often left implicit in the IFC file thus inhibiting the possibility for automated processing.\n",
18 | "\n",
19 | "## The solution\n",
20 | "In both scenarios, the georeferencing information can be made explicit by using the **IfcProjectedCRS** and **IfcMapConversion** entities from the [IFC4 standard](https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/templates/project-global-positioning.htm). The former allows specifying a recognized, target coordinate reference system (CRS). The latter can be used to specify the coordinate transformation needed to convert coordinates from the project CRS to the specified target CRS. As depicted in the diagram below from the IFC standard, these ifc entities are directly related to the IfcProject (a subclass of IfcContext) entity.\n",
21 | "\n",
22 | "[](https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/templates/project-global-positioning.htm)\n",
23 | "\n",
24 | "[IfcProjectedCRS](https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcrepresentationresource/lexical/ifcprojectedcrs.htm) allows specifying a target projected coordinate reference system. The most relevant attribute is the name of the reference system:\n",
25 | "* **IfcProjectedCRS.Name**: According to the IFC4 specification, the name shall be taken from the list recognized by the European Petroleum Survey Group EPSG and should be qualified by the EPSG name space, for example as '[EPSG:9897](https://epsg.io/9897)'. In case the EPSG registry includes a vertical datum for the coordinate reference system - as is the case for our example - no other properties need to be provided.\n",
26 | "\n",
27 | "[IfcMapConversion](https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcrepresentationresource/lexical/ifcmapconversion.htm) provides information on how the reference system of the IFC file can be converted into the reference system indicated by IfcProjectedCRS. It contains information on the required coordinate translation, rotation, and scaling:\n",
28 | "* **IfcMapConversion.Eastings, IfcMapConversion.Northings, IfcMapConversion.OrthogonalHeight** indicate the position of the origin of the local reference system used in the IFC file in terms of the indicated IfcProjectedCRS. Together, they define the coordinate *translation* needed to convert the *origin* of the local project CRS into the origin of the target geospatial CRS.\n",
29 | "* **IfcMapConversion. XAxisAbscissa and IfcMapConversion.XAxisOrdinate** together define the angle of *rotation* required to convert the axis orientation of the local reference system, into the axis orientation of the indicated coordinate reference system.\n",
30 | "* **IfcMapConversion.Scale** indicates the conversion factor to be used, to convert the units of the local, engineering coordinate system into the units of the target CRS (often expressed in metres). If omitted, the value of 1.0 is assumed.\n",
31 | "\n",
32 | "In IFC2x3 the same information can be included by using equivalent property sets 'ePset_MapConversion' and 'ePSet_ProjectedCRS' on IfcSite as proposed by BuildingSMART Australasia in their paper '[User guide for georeferencing in IFC](https://www.buildingsmart.org/wp-content/uploads/2020/02/User-Guide-for-Geo-referencing-in-IFC-v2.0.pdf)'.\n",
33 | "\n",
34 | "Recent versions of [Revit](https://wiki.osarch.org/index.php?title=Revit_IFC_geolocation) (with the [revit-ifc plugin](https://github.com/Autodesk/revit-ifc)) and [ArchiCAD](https://wiki.osarch.org/index.php?title=ArchiCAD_IFC_geolocation) make it possible export this IFC information. See the osarch.org wiki or the [blog post](https://thinkmoult.com/ifc-coordinate-reference-systems-and-revit.html) by Dion Moult for a detailed guide on georeferencing in Revit."
35 | ]
36 | },
37 | {
38 | "cell_type": "markdown",
39 | "id": "6186672f-f56d-40cf-a258-f420152728a4",
40 | "metadata": {},
41 | "source": [
42 | "## Example: adding georeference information with IfcOpenShell-python\n",
43 | "\n",
44 | "[IfcOpenShell-python](https://github.com/IfcOpenShell/IfcOpenShell) is an open-source library for working with IFC files. Let's use [IfcOpenShell-python](https://github.com/IfcOpenShell/IfcOpenShell) to write or read georeferencing information from an IFC file.\n",
45 | "For this purpose, we have taken the [Duplex Apartment](https://github.com/buildingSMART/Sample-Test-Files/tree/master/IFC%202x3/Duplex%20Apartment) IFC file by BuildingSMART International.\n",
46 | "\n",
47 | "### Visualize the spaces in the IFC file\n",
48 | "\n",
49 | "First, let's visualize the rooms (IfcSpace) in the IFC file using a threeJS viewer. This filter on IfcSpace objects is relevant because we usually do not need all the detail in the IFC file when combining it with GIS data. The viewer shows the XYZ axis and origin of the local reference system. Its origin is located very near the construction site.\n",
50 | "\n"
51 | ]
52 | },
53 | {
54 | "cell_type": "code",
55 | "execution_count": 1,
56 | "id": "6cf36317",
57 | "metadata": {},
58 | "outputs": [
59 | {
60 | "data": {
61 | "application/vnd.jupyter.widget-view+json": {
62 | "model_id": "df5df7660e144e4cbb74b57bee8ec2a2",
63 | "version_major": 2,
64 | "version_minor": 0
65 | },
66 | "text/plain": [
67 | "HBox(children=(VBox(children=(HBox(children=(Checkbox(value=True, description='Axes', layout=Layout(height='au…"
68 | ]
69 | },
70 | "metadata": {},
71 | "output_type": "display_data"
72 | }
73 | ],
74 | "source": [
75 | "import ifcopenshell\n",
76 | "import ifcopenshell.geom\n",
77 | "from OCC.Display.WebGl.jupyter_renderer import JupyterRenderer, format_color\n",
78 | "\n",
79 | "settings = ifcopenshell.geom.settings()\n",
80 | "settings.set(settings.USE_WORLD_COORDS, True)\n",
81 | "settings.set(settings.USE_BREP_DATA, True)\n",
82 | "settings.set(settings.USE_PYTHON_OPENCASCADE, True)\n",
83 | "\n",
84 | "fn = './Duplex_A_20110907.ifc'\n",
85 | "ifc_file = ifcopenshell.open(fn)\n",
86 | "\n",
87 | "threejs_renderer = JupyterRenderer(size=(500, 500))\n",
88 | "spaces = ifc_file.by_type(\"IfcSpace\")\n",
89 | "\n",
90 | "for space in spaces:\n",
91 | " if space.Representation is not None:\n",
92 | " shape = ifcopenshell.geom.create_shape(settings, inst=space)\n",
93 | " r,g,b,alpha = shape.styles[0]\n",
94 | " color = format_color(int(abs(r)*255), int(abs(g)*255), int(abs(b)*255))\n",
95 | " threejs_renderer.DisplayShape(shape.geometry, shape_color = color, transparency=True, opacity=alpha, render_edges=True)\n",
96 | "\n",
97 | "threejs_renderer.Display()"
98 | ]
99 | },
100 | {
101 | "cell_type": "markdown",
102 | "id": "c4a6c11f-108c-404c-aa36-9101fb190f8c",
103 | "metadata": {},
104 | "source": [
105 | "### Write georeference parameters\n",
106 | "Now, let's use IfcOpenShell-python to add geoloation information to the IFC file with the function `set_mapconversion_crs()` in [georeference_ifc/main.py](georeference_ifc/main.py)."
107 | ]
108 | },
109 | {
110 | "cell_type": "code",
111 | "execution_count": 2,
112 | "id": "a62c30a2-127e-4442-9019-a2e6dab01988",
113 | "metadata": {},
114 | "outputs": [
115 | {
116 | "name": "stdout",
117 | "output_type": "stream",
118 | "text": [
119 | "output written to ./Duplex_A_20110907_georeferenced.ifc\n"
120 | ]
121 | }
122 | ],
123 | "source": [
124 | "import georeference_ifc\n",
125 | "import re\n",
126 | "\n",
127 | "georeference_ifc.set_mapconversion_crs(ifc_file=ifc_file,\n",
128 | " target_crs_epsg_code='EPSG:9897',\n",
129 | " eastings=76670.0,\n",
130 | " northings=77179.0,\n",
131 | " orthogonal_height=293.700012207031,\n",
132 | " x_axis_abscissa=0.325568154457152,\n",
133 | " x_axis_ordinate=0.945518575599318,\n",
134 | " scale=1.0)\n",
135 | "fn_output = re.sub('\\.ifc$','_georeferenced.ifc', fn)\n",
136 | "ifc_file.write(fn_output)\n",
137 | "print(f'output written to {fn_output}')"
138 | ]
139 | },
140 | {
141 | "cell_type": "markdown",
142 | "id": "6f3dc5ac-284d-4efd-9f44-4aca3a9f18cf",
143 | "metadata": {},
144 | "source": [
145 | "### Read georeference information\n",
146 | "\n",
147 | "The function `get_mapconversion_crs()` in [georeference_ifc/main.py](georeference_ifc/main.py) can be used to extract georeferencing information from an IFC file. From XAxisAbscissa and XAxisOrdinate we calculate the rotation."
148 | ]
149 | },
150 | {
151 | "cell_type": "code",
152 | "execution_count": 3,
153 | "id": "8852b5a9-e67f-4257-8037-755dd284fe0a",
154 | "metadata": {
155 | "tags": []
156 | },
157 | "outputs": [
158 | {
159 | "data": {
160 | "text/html": [
161 | "\n",
162 | "\n",
175 | "
\n",
176 | " \n",
177 | " \n",
178 | " | \n",
179 | " property | \n",
180 | " value | \n",
181 | "
\n",
182 | " \n",
183 | " \n",
184 | " \n",
185 | " 0 | \n",
186 | " Name | \n",
187 | " EPSG:9897 | \n",
188 | "
\n",
189 | " \n",
190 | " 1 | \n",
191 | " id | \n",
192 | " 39126 | \n",
193 | "
\n",
194 | " \n",
195 | " 2 | \n",
196 | " Eastings | \n",
197 | " 76670.0 | \n",
198 | "
\n",
199 | " \n",
200 | " 3 | \n",
201 | " Northings | \n",
202 | " 77179.0 | \n",
203 | "
\n",
204 | " \n",
205 | " 4 | \n",
206 | " OrthogonalHeight | \n",
207 | " 293.700012 | \n",
208 | "
\n",
209 | " \n",
210 | " 5 | \n",
211 | " XAxisAbscissa | \n",
212 | " 0.325568 | \n",
213 | "
\n",
214 | " \n",
215 | " 6 | \n",
216 | " XAxisOrdinate | \n",
217 | " 0.945519 | \n",
218 | "
\n",
219 | " \n",
220 | " 7 | \n",
221 | " Scale | \n",
222 | " 1.0 | \n",
223 | "
\n",
224 | " \n",
225 | " 8 | \n",
226 | " id | \n",
227 | " 39116 | \n",
228 | "
\n",
229 | " \n",
230 | "
\n",
231 | "
"
232 | ],
233 | "text/plain": [
234 | " property value\n",
235 | "0 Name EPSG:9897\n",
236 | "1 id 39126\n",
237 | "2 Eastings 76670.0\n",
238 | "3 Northings 77179.0\n",
239 | "4 OrthogonalHeight 293.700012\n",
240 | "5 XAxisAbscissa 0.325568\n",
241 | "6 XAxisOrdinate 0.945519\n",
242 | "7 Scale 1.0\n",
243 | "8 id 39116"
244 | ]
245 | },
246 | "metadata": {},
247 | "output_type": "display_data"
248 | },
249 | {
250 | "name": "stdout",
251 | "output_type": "stream",
252 | "text": [
253 | "Rotation is: 71.0° (degrees(atan2(map_conversion.XAxisOrdinate, map_conversion.XAxisAbscissa)) \n"
254 | ]
255 | }
256 | ],
257 | "source": [
258 | "import georeference_ifc\n",
259 | "\n",
260 | "IfcMapConversion, IfcProjectedCRS = georeference_ifc.get_mapconversion_crs(ifc_file=ifc_file)\n",
261 | "\n",
262 | "\n",
263 | "import pandas as pd\n",
264 | "from IPython.display import display\n",
265 | "df = pd.DataFrame(list(IfcProjectedCRS.__dict__.items()) + list(IfcMapConversion.__dict__.items()), columns= ['property', 'value'])\n",
266 | "display(df)\n",
267 | "\n",
268 | "rotation = georeference_ifc.get_rotation(IfcMapConversion)\n",
269 | "print(f'Rotation is: {rotation:.1f}° (degrees(atan2(map_conversion.XAxisOrdinate, map_conversion.XAxisAbscissa)) ')"
270 | ]
271 | },
272 | {
273 | "cell_type": "markdown",
274 | "id": "a6400098",
275 | "metadata": {
276 | "tags": []
277 | },
278 | "source": [
279 | "### Convert 3D spaces into 2D polygons (footprint)\n",
280 | "\n",
281 | "We will use a utility function `shape_to_polygons(shape)` that will convert a 3D space into a 2D polygon, by extracting the shape into faces. Only the first face that is converted into a valid polygon is taken. We then use [GeoPandas](https://geopandas.org) to convert the polygons from the project CRS into the geospatial CRS using the IfcMapConversion parameters. We will respectively apply a rotation, translation, and scaling."
282 | ]
283 | },
284 | {
285 | "cell_type": "code",
286 | "execution_count": 4,
287 | "id": "3a87c993",
288 | "metadata": {},
289 | "outputs": [],
290 | "source": [
291 | "from util import shape_to_polygons\n",
292 | "import geopandas as gpd\n",
293 | "from geopandas import GeoSeries\n",
294 | "\n",
295 | "polygons = []\n",
296 | "names = []\n",
297 | "for space in spaces:\n",
298 | " if space.Representation is not None:\n",
299 | " shape = ifcopenshell.geom.create_shape(settings, inst=space)\n",
300 | " shape_polygons = shape_to_polygons(shape)\n",
301 | " polygons = polygons + shape_polygons\n",
302 | " names = names + [space.Name for _ in shape_polygons]\n",
303 | "\n",
304 | "\n",
305 | "footprint = GeoSeries(polygons,crs=IfcProjectedCRS.Name)\n",
306 | "footprint = footprint\\\n",
307 | " .rotate(rotation, origin=(0,0,), use_radians=False)\\\n",
308 | " .translate(IfcMapConversion.Eastings, IfcMapConversion.Northings, 0)\\\n",
309 | " .scale(IfcMapConversion.Scale if IfcMapConversion.Scale else 1.0)"
310 | ]
311 | },
312 | {
313 | "cell_type": "markdown",
314 | "id": "929967ec-5c3c-47b4-8cc3-feb2b211f642",
315 | "metadata": {},
316 | "source": [
317 | "### Add footprints to map\n",
318 | "The resulting building footprint data is pure geospatial data that can be depicted on a map and combined with other geospatial data, in this example with [OpenStreetMap](https://www.openstreetmap.org) and [geoportail.lu](https://map.geoportail.lu/) data."
319 | ]
320 | },
321 | {
322 | "cell_type": "code",
323 | "execution_count": 5,
324 | "id": "2f2fdf62-4820-41d9-829a-50464af0c459",
325 | "metadata": {},
326 | "outputs": [
327 | {
328 | "data": {
329 | "text/html": [
330 | "Make this Notebook Trusted to load map: File -> Trust Notebook
"
1273 | ],
1274 | "text/plain": [
1275 | ""
1276 | ]
1277 | },
1278 | "execution_count": 5,
1279 | "metadata": {},
1280 | "output_type": "execute_result"
1281 | }
1282 | ],
1283 | "source": [
1284 | "import folium\n",
1285 | "import requests\n",
1286 | "\n",
1287 | "m = folium.Map(location=[49.629, 6.122], zoom_start=20, tiles='CartoDB positron', max_zoom=30)\n",
1288 | "\n",
1289 | "url=\"https://wms.inspire.geoportail.lu/geoserver/cp/wfs?service=WFS&request=GetFeature&version=2.0.0&srsName=urn:ogc:def:crs:EPSG::3857&typeNames=cp%3ACP.CadastralParcel&bbox=681342.6715153919,6382237.960593072,681731.4043978148,6382399.0656238245,urn:ogc:def:crs:EPSG::3857&namespaces=xmlns(cp,http%3A%2F%2Fcp)&count=2000&outputFormat=json\"\n",
1290 | "df_cp = gpd.read_file(requests.get(url).text, crs='EPSG:3857')\n",
1291 | "df_cp = df_cp.to_crs(epsg=4326)\n",
1292 | "folium.GeoJson(df_cp.to_json(), name=\"cadastral parcels\").add_to(m)\n",
1293 | "\n",
1294 | "df = gpd.GeoDataFrame({'name': names}, geometry=footprint)\n",
1295 | "df = df.to_crs(epsg=4326)\n",
1296 | "for _, r in df.iterrows():\n",
1297 | " sim_geo = gpd.GeoSeries(r['geometry'])\n",
1298 | " geo_j = sim_geo.to_json()\n",
1299 | " geo_j = folium.GeoJson(data=geo_j,\n",
1300 | " style_function=lambda x: {'fillColor': 'orange'})\n",
1301 | " folium.Popup(r['name']).add_to(geo_j)\n",
1302 | " geo_j.add_to(m)\n",
1303 | "\n",
1304 | "m"
1305 | ]
1306 | },
1307 | {
1308 | "cell_type": "markdown",
1309 | "id": "b5f7b734-45c0-48f7-879e-2c5c7407cbd1",
1310 | "metadata": {},
1311 | "source": [
1312 | "## Conclusion\n",
1313 | "\n",
1314 | "The use of **IfcProjectedCRS** and **IfcMapConversion** is a precise and unitrusive way of adding geolocation information to an IFC file.\n",
1315 | "* This solution does not require you to change the local reference system, only to provide information about it.\n",
1316 | "* It is more accurate compared the use of IfcSite.RefLongitude and IfcSite.RefLatitude in [WGS84](https://epsg.io/4326) coordinates, as a more appropriate geospatial coordinate reference system can be used.\n",
1317 | "* BIM tools such as Revit and ArchiCAD already support it.\n",
1318 | "* If your BIM tool does not support it, you can use IfcOpenShell to postprocess your IFC prior to exchanging it with third parties.\n",
1319 | "\n",
1320 | "Therefore providing this information should considered to be made mandatory when drafting BIM information exchange agreements."
1321 | ]
1322 | },
1323 | {
1324 | "cell_type": "markdown",
1325 | "id": "e0ff92a0-6ced-45e2-9d4b-ee6a8330b894",
1326 | "metadata": {},
1327 | "source": [
1328 | "## References\n",
1329 | "\n",
1330 | "BuildingSMART. IFC Specifications database. https://technical.buildingsmart.org/standards/ifc/ifc-schema-specifications/\n",
1331 | "\n",
1332 | "BuildingSMART Australasia (2020). User Guide for Geo-referencing in IFC, version 2.0. https://www.buildingsmart.org/wp-content/uploads/2020/02/User-Guide-for-Geo-referencing-in-IFC-v2.0.pdf\n",
1333 | "\n",
1334 | "Dion Moult (2019). IFC Coordinate Reference Systems and Revit. https://thinkmoult.com/ifc-coordinate-reference-systems-and-revit.html\n",
1335 | "\n",
1336 | "OSarch.org (2021). IFC2x3 geo-referencing property set template definitions. https://wiki.osarch.org/index.php?title=IFC_geolocation\n",
1337 | "\n",
1338 | "Clemen, C., & Görne, H. (2019). Level of Georeferencing (LoGeoRef) using IFC for BIM. Journal of Geodesy, Cartography and Cadastre, (10). https://jgcc.geoprevi.ro/docs/2019/10/jgcc_2019_no10_3.pdf\n",
1339 | "\n",
1340 | "Noardo, F., Krijnen, T., Arroyo Ohori, K., Biljecki, F., Ellul, C., Harrie, L., Eriksson, H., Polia, L., Salheb, N., Tauscher, H., van Liempt, J., Goerne, H., Hintz, D., Kaiser, T., Leoni, C., Warchol, A., Stoter, J. (2021). Reference study of IFC software support: the GeoBIM benchmark 2019 – Part I. Transactions in GIS.\n",
1341 | "\n",
1342 | "\n",
1343 | "## Comments\n",
1344 | "Comments are welcome via GitHub [issues](https://github.com/stijngoedertier/georeference-ifc).\n",
1345 | "\n",
1346 | "\n",
1347 | "## License\n",
1348 | "\n",
1349 | "This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International [(CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/) License.\n"
1350 | ]
1351 | }
1352 | ],
1353 | "metadata": {
1354 | "kernelspec": {
1355 | "display_name": "Python 3 (ipykernel)",
1356 | "language": "python",
1357 | "name": "python3"
1358 | },
1359 | "language_info": {
1360 | "codemirror_mode": {
1361 | "name": "ipython",
1362 | "version": 3
1363 | },
1364 | "file_extension": ".py",
1365 | "mimetype": "text/x-python",
1366 | "name": "python",
1367 | "nbconvert_exporter": "python",
1368 | "pygments_lexer": "ipython3",
1369 | "version": "3.10.6"
1370 | }
1371 | },
1372 | "nbformat": 4,
1373 | "nbformat_minor": 5
1374 | }
1375 |
--------------------------------------------------------------------------------
/postBuild:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stijngoedertier/georeference-ifc/3cf8976c445e32944524977f5713cab4f11e4fa1/postBuild
--------------------------------------------------------------------------------
/tests/test_georeference_ifc.py:
--------------------------------------------------------------------------------
1 | import math
2 | import unittest
3 | import ifcopenshell
4 | from georeference_ifc import get_mapconversion_crs, get_rotation, set_si_units, set_mapconversion_crs
5 |
6 |
7 | class SimpleTest(unittest.TestCase):
8 |
9 | def test_ifc4(self):
10 | fn_input = 'ACT2BIM.ifc'
11 | ifc_file = ifcopenshell.open(fn_input)
12 | set_si_units(ifc_file)
13 | set_mapconversion_crs(ifc_file=ifc_file,
14 | target_crs_epsg_code='EPSG:9897',
15 | eastings=76670.0,
16 | northings=77179.0,
17 | orthogonal_height=293.7,
18 | x_axis_abscissa=-0.325568154457152,
19 | x_axis_ordinate=0.945518575599318,
20 | scale=1.0)
21 | fn_output = 'ACT2BIM_georeferenced.ifc'
22 | ifc_file.write(fn_output)
23 |
24 | ifc_file = ifcopenshell.open(fn_output)
25 | mapconversion, crs = get_mapconversion_crs(ifc_file)
26 | assert math.isclose(mapconversion.Eastings, 76670.0)
27 | assert math.isclose(mapconversion.Northings, 77179.0)
28 | assert math.isclose(mapconversion.OrthogonalHeight, 293.7)
29 | rotation = get_rotation(mapconversion)
30 | assert math.isclose(rotation, 109.0)
31 |
32 | def test_ifc2x3(self):
33 | fn_input = 'Duplex_A_20110907.ifc'
34 | ifc_file = ifcopenshell.open(fn_input)
35 | set_si_units(ifc_file)
36 | set_mapconversion_crs(ifc_file=ifc_file,
37 | target_crs_epsg_code='EPSG:2169',
38 | eastings=76670.0,
39 | northings=77179.0,
40 | orthogonal_height=293.7,
41 | x_axis_abscissa=0.325568154457152,
42 | x_axis_ordinate=0.945518575599318,
43 | scale=1.0)
44 | fn_output = 'Duplex_A_20110907_georeferenced.ifc'
45 | ifc_file.write(fn_output)
46 |
47 | ifc_file = ifcopenshell.open(fn_output)
48 | mapconversion, crs = get_mapconversion_crs(ifc_file)
49 | assert math.isclose(mapconversion.Eastings, 76670.0)
50 | assert math.isclose(mapconversion.Northings, 77179.0)
51 | assert math.isclose(mapconversion.OrthogonalHeight, 293.7)
52 | rotation = get_rotation(mapconversion)
53 | assert math.isclose(rotation, 71.0)
54 |
55 |
56 | if __name__ == '__main__':
57 | unittest.main()
58 |
--------------------------------------------------------------------------------
/util.py:
--------------------------------------------------------------------------------
1 | from OCC.Core.TopoDS import TopoDS_Shape, TopoDS_Compound, TopoDS_Builder, topods
2 | from OCC.Core.GeomProjLib import geomprojlib
3 | from OCC.Core.BRep import BRep_Tool
4 | from OCC.Core.BRepTools import breptools, BRepTools_WireExplorer
5 | from OCC.Core.TopExp import TopExp_Explorer
6 | from OCC.Core.TopAbs import TopAbs_FACE, TopAbs_WIRE, TopAbs_EDGE, TopAbs_VERTEX
7 | from OCC.Core.gp import gp_Pln, gp_Pnt, gp_Dir
8 | from OCC.Core.Geom import Geom_Plane
9 | from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeFace, BRepBuilderAPI_MakeWire, BRepBuilderAPI_MakeEdge
10 | from OCC.Core.ShapeFix import ShapeFix_Wire
11 |
12 | from shapely.geometry import Polygon
13 |
14 |
15 | def make_wire(edges):
16 | make_wire = BRepBuilderAPI_MakeWire()
17 | for e in edges:
18 | make_wire.Add(e)
19 | make_wire.Build()
20 | if make_wire.IsDone():
21 | wire = make_wire.Wire()
22 | return wire
23 | else:
24 | return None
25 |
26 |
27 | def shape_to_polygons(shape):
28 | polygons = []
29 | exp1 = TopExp_Explorer(shape.geometry, TopAbs_FACE)
30 | polygon_extracted = False
31 | while exp1.More() and not polygon_extracted:
32 | face = exp1.Current()
33 | surface = BRep_Tool.Surface(face)
34 | wire = breptools.OuterWire(face)
35 | fix = ShapeFix_Wire()
36 | fix.Load(wire)
37 | fix.Perform()
38 | #wire = fix.Wire()
39 | exp2 = BRepTools_WireExplorer(wire)
40 | points = []
41 | while exp2.More():
42 | vertex = exp2.CurrentVertex()
43 | pnt = BRep_Tool.Pnt(vertex)
44 | points.append([pnt.X(),pnt.Y()])
45 | exp2.Next()
46 | polygon = Polygon(points)
47 | if polygon.area > 0:# and polygon.is_valid:
48 | polygons.append(polygon)
49 | polygon_extracted = True
50 | exp1.Next()
51 | return polygons
--------------------------------------------------------------------------------