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