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