]> git.openstreetmap.org Git - nominatim-ui.git/blob - src/pages/DetailsPage.svelte
details page: entrances list and on map (#301)
[nominatim-ui.git] / src / pages / DetailsPage.svelte
1 <script>
2   import { untrack } from 'svelte';
3   import { update_html_title } from '../lib/api_utils.js';
4   import { appState } from '../state/AppState.svelte.js';
5
6   import {
7     coverageType, isAdminBoundary,
8     formatAddressRank, formatKeywordToken, formatOSMType
9   } from '../lib/helpers.js';
10   import Header from '../components/Header.svelte';
11   import MapIcon from '../components/MapIcon.svelte';
12   import SearchSectionDetails from '../components/SearchSectionDetails.svelte';
13   import DetailsOneRow from '../components/DetailsOneRow.svelte';
14   import DetailsLink from '../components/DetailsLink.svelte';
15   import DetailsPostcodeHint from '../components/DetailsPostcodeHint.svelte';
16   import InfoRowList from '../components/DetailsInfoRowList.svelte';
17   import WikipediaLink from '../components/WikipediaLink.svelte';
18   import OsmLink from '../components/OsmLink.svelte';
19   import Map from '../components/Map.svelte';
20
21   let aPlace = $state();
22   let base_url = $state();
23   let api_request_params = $state.raw();
24   let api_request_finished = $state(false);
25
26   function loaddata(search_params) {
27     api_request_params = {
28       place_id: search_params.get('place_id'),
29       osmtype: search_params.get('osmtype'),
30       osmid: search_params.get('osmid'),
31       class: search_params.get('class'),
32       keywords: search_params.get('keywords'),
33       addressdetails: 1,
34       hierarchy: (search_params.get('hierarchy') === '1' ? 1 : 0),
35       group_hierarchy: 1,
36       entrances: (search_params.get('entrances') === '1' ? 1 : 0),
37       polygon_geojson: 1,
38       format: 'json'
39     };
40     api_request_finished = false;
41
42     if (api_request_params.place_id || (api_request_params.osmtype && api_request_params.osmid)) {
43
44       if (api_request_params.place_id) {
45         update_html_title('Details for ' + api_request_params.place_id);
46       } else {
47         update_html_title('Details for ' + api_request_params.osmtype + api_request_params.osmid);
48       }
49
50       appState.fetchFromApi('details', api_request_params, function (data) {
51         window.scrollTo(0, 0);
52         api_request_finished = true;
53         aPlace = (data && !data.error) ? data : undefined;
54       });
55     } else {
56       aPlace = undefined;
57     }
58   }
59
60   function place_has_keywords(aThisPlace) {
61     // Return false if Nominatim API sends 'keywords: { name: [], address: [] }'
62     // Like no longer needed after Nominatim version 4.3
63     return (
64       aThisPlace.keywords && aThisPlace.keywords.name && aThisPlace.keywords.address
65       && (aThisPlace.keywords.name.length > 0 || aThisPlace.keywords.address.length > 0)
66     );
67   }
68
69   function country_code(aThisPlace) {
70     let aLine = aThisPlace.address.find((address_line) => address_line.type === 'country_code');
71     return aLine ? aLine.localname : null;
72   }
73
74   $effect(() => {
75     if (appState.page.tab === 'details') {
76       const params = appState.page.params;
77       untrack(() => {
78         loaddata(params);
79         base_url = window.location.search;
80       });
81     }
82   });
83
84   const reverse_only = Nominatim_Config.Reverse_Only;
85 </script>
86
87 {#snippet subheader()}
88   <SearchSectionDetails api_request_params={api_request_params}/>
89 {/snippet}
90 <Header {subheader} />
91
92 <div class="container">
93   {#if aPlace}
94     <div class="row">
95       <div class="col-sm-10">
96         <h1>
97           {aPlace.localname || `${formatOSMType(aPlace.osm_type)} ${aPlace.osm_id}` }
98           <small><DetailsLink feature={aPlace} text="link to this page" /></small>
99         </h1>
100       </div>
101       <div class="col-sm-2 text-end">
102         <MapIcon aPlace={aPlace} />
103       </div>
104     </div>
105     <div class="row">
106       <div class="col-md-6">
107         <table id="locationdetails" class="table table-striped table-responsive">
108           <tbody>
109             <tr class="info-row"><td>Name</td><td>
110             {#if aPlace.names && typeof (aPlace.names) === 'object'
111               && Object.keys(aPlace.names).length}
112               <InfoRowList items={aPlace.names} />
113             {:else}
114               <span class="noname fw-bold">No Name</span>
115             {/if}
116             </td></tr>
117             <tr class="info-row"><td>Type</td><td>{aPlace.category}:{aPlace.type}</td></tr>
118             <tr class="info-row"><td>Last Updated</td><td>{aPlace.indexed_date}</td></tr>
119             {#if (isAdminBoundary(aPlace)) }
120               <tr class="info-row"><td>Admin Level</td><td>{aPlace.admin_level}</td></tr>
121             {/if}
122             <tr class="info-row"><td>Search Rank</td><td>{aPlace.rank_search}</td></tr>
123             <tr class="info-row"><td>Address Rank</td><td>
124               {aPlace.rank_address} ({formatAddressRank(aPlace.rank_address)})
125             </td></tr>
126             {#if aPlace.calculated_importance}
127               <tr class="info-row"><td>Importance</td><td>
128                   {aPlace.calculated_importance}
129                   {#if !aPlace.importance} (estimated){/if}
130               </td></tr>
131             {/if}
132             <tr class="info-row"><td>Coverage</td><td>{coverageType(aPlace)}</td></tr>
133             <tr class="info-row"><td>Centre Point (lat,lon)</td><td>
134                 {aPlace.centroid.coordinates[1]},{aPlace.centroid.coordinates[0]}
135             </td></tr>
136             <tr class="info-row"><td>OSM</td><td>
137               <OsmLink osmType={aPlace.osm_type} osmId={aPlace.osm_id}/>
138             </td></tr>
139             <tr class="info-row"><td>Place Id</td><td>
140                {aPlace.place_id}
141                (<a href="https://nominatim.org/release-docs/develop/api/Output/#place_id-is-not-a-persistent-id">
142                  on this server
143                </a>)
144             </td></tr>
145             {#if aPlace.calculated_wikipedia}
146               <tr class="info-row"><td>Wikipedia Calculated</td><td>
147               <WikipediaLink wikipedia={aPlace.calculated_wikipedia} />
148               </td></tr>
149             {/if}
150             <tr class="info-row"><td>Computed Postcode</td><td>
151               {#if aPlace.calculated_postcode}
152                 {aPlace.calculated_postcode}
153                 <DetailsPostcodeHint postcode={aPlace.calculated_postcode}
154                                      lat={aPlace.centroid.coordinates[1]}
155                                      lon={aPlace.centroid.coordinates[0]} />
156               {/if}
157             </td></tr>
158             <tr class="info-row"><td>Address Tags</td><td>
159               <InfoRowList items={aPlace.addresstags} />
160             </td></tr>
161             <tr class="info-row"><td>Extra Tags</td><td>
162               <InfoRowList items={aPlace.extratags} />
163             </td></tr>
164           </tbody>
165         </table>
166       </div>
167       <div class="col-md-6">
168         <div id="map-wrapper">
169           <Map current_result={aPlace} />
170         </div>
171       </div>
172     </div>
173     <div class="row">
174       <div class="col-md-12">
175         <h2>Address</h2>
176         <table id="address" class="table table-striped table-small">
177           <thead>
178             <tr>
179               <th>Local name</th>
180               <th>Type</th>
181               <th>OSM</th>
182               <th>Address rank</th>
183               <th>Admin level</th>
184               <th>Distance</th>
185               <th></th>
186             </tr>
187           </thead>
188           <tbody>
189             {#if aPlace.address}
190               {#each aPlace.address as addressLine}
191                 <DetailsOneRow addressLine={addressLine}
192                                bMarkUnusedLines={true}
193                                bDistanceInMeters={false}
194                                sCountryCode={country_code(aPlace)} />
195               {/each}
196             {/if}
197
198             {#if aPlace.linked_places}
199               <tr class="all-columns"><td colspan="7"><h2>Linked Places</h2></td></tr>
200               {#each aPlace.linked_places as addressLine}
201                 <DetailsOneRow addressLine={addressLine}
202                                bMarkUnusedLines={true}
203                                bDistanceInMeters={true} />
204               {/each}
205             {/if}
206
207             {#if !reverse_only}
208               <tr class="all-columns"><td colspan="7"><h2>Keywords</h2></td></tr>
209               {#if api_request_params.keywords}
210
211                 {#if place_has_keywords(aPlace)}
212                   <tr class="all-columns"><td colspan="7"><h3>Name Keywords</h3></td></tr>
213                   {#each aPlace.keywords.name as keyword}
214                     <tr>
215                       <td>{formatKeywordToken(keyword.token)}</td>
216                       {#if keyword.id}
217                         <td>word id: {keyword.id}</td>
218                       {/if}
219                     </tr>
220                   {/each}
221
222                   {#if aPlace.keywords.address}
223                     <tr class="all-columns"><td colspan="7"><h3>Address Keywords</h3></td></tr>
224                     {#each aPlace.keywords.address as keyword}
225                       <tr>
226                         <td>{formatKeywordToken(keyword.token)}</td>
227                         {#if keyword.id}
228                           <td>word id: {keyword.id}</td>
229                         {/if}
230                       </tr>
231                     {/each}
232                   {/if}
233                 {:else}
234                   <tr><td>Place has no keywords</td></tr>
235                 {/if}
236               {:else}
237                 <tr>
238                   <td>
239                      <a class="btn btn-outline-secondary btn-sm"
240                       href="{base_url}&keywords=1">display keywords</a>
241                   </td>
242                 </tr>
243               {/if}
244             {/if}
245
246             <tr class="all-columns"><td colspan="7"><h2>Parent Of</h2></td></tr>
247             {#if api_request_params.hierarchy}
248               {#if aPlace.hierarchy && typeof (aPlace.hierarchy) === 'object'
249                 && Object.keys(aPlace.hierarchy).length}
250                 {#each Object.keys(aPlace.hierarchy) as type}
251                   <tr class="all-columns"><td colspan="7"><h3>{type}</h3></td></tr>
252                   {#each aPlace.hierarchy[type] as line}
253                     <DetailsOneRow addressLine={line} bDistanceInMeters={true} />
254                  {/each}
255                 {/each}
256
257                 {#if Object.keys(aPlace.hierarchy) > 500}
258                   <tr><td><p>There are more child objects which are not shown.</p></td></tr>
259                 {/if}
260               {:else}
261                 <tr><td>Place is not parent of other places</td></tr>
262               {/if}
263             {:else}
264               <tr>
265                 <td>
266                    <a class="btn btn-outline-secondary btn-sm"
267                     href="{base_url}&hierarchy=1">display child places</a>
268                 </td>
269               </tr>
270             {/if}
271
272             <tr class="all-columns"><td colspan="7"><h2>Entrances</h2></td></tr>
273             {#if api_request_params.entrances}
274               {#if aPlace.entrances && aPlace.entrances.length}
275                 <tr>
276                   <td></td>
277                   <td>Entrance Type</td>
278                   <td>OSM</td>
279                   <td>Extra Tags</td>
280                 </tr>
281                 {#each aPlace.entrances as entrance, i}
282                   <tr class="all-columns">
283                     <td>{i + 1}</td>
284                     <td>{entrance.type}</td>
285                     <td><OsmLink osmType='N' osmId={entrance.osm_id} /></td>
286                     <td><InfoRowList items={entrance.extratags} /></td>
287                   </tr>
288                 {/each}
289               {:else}
290                 <tr><td>Place does not have entrances</td></tr>
291               {/if}
292             {:else}
293               <tr>
294                 <td>
295                    <a class="btn btn-outline-secondary btn-sm"
296                     href="{base_url}&entrances=1">display entraces</a>
297                 </td>
298               </tr>
299             {/if}
300
301
302
303           </tbody>
304         </table>
305       </div>
306     </div>
307   {:else if (window.location.search !== '' && api_request_finished)}
308     No such place found.
309   {/if}
310 </div>
311
312
313
314 <style>
315   h1 {
316     margin: 10px 0;
317     padding-left: 8px;
318   }
319
320   h1 small :global(a) {
321     font-size: 0.5em;
322     white-space: nowrap;
323   }
324
325   h2 {
326     font-size: 2em;
327     padding-left: 8px;
328     background-color: var(--bs-body-bg);
329   }
330   h3 {
331     font-size: 1.5em;
332     padding-left: 8px;
333   }
334
335   tr.all-columns {
336     background-color: var(--bs-body-bg) !important;
337     border: none;
338   }
339   tr.all-columns td {
340     border-top: none !important;
341     padding-left: 0 !important;
342   }
343   :global(span.noname){
344     color: var(--bs-danger);
345   }
346
347   tr.info-row td {
348     padding: 2px 8px;
349     font-size: 0.9em;
350   }
351
352   #map-wrapper {
353     position: relative;
354     width:100%;
355     min-height: auto;
356     height:300px;
357     border: 1px solid #666;
358   }
359 </style>