Wikipedia csbwiki https://csb.wikipedia.org/wiki/Prz%C3%A9dn%C3%B4_starna MediaWiki 1.47.0-wmf.9 first-letter Media Specjalnô Diskùsëjô Brëkòwnik Diskùsëjô brëkòwnika Wiki Diskùsëjô Wiki Òbrôzk Diskùsëjô òbrôzków MediaWiki Diskùsëjô MediaWiki Szablóna Diskùsëjô Szablónë Pòmòc Diskùsëjô Pòmòcë Kategòrëjô Diskùsëjô Kategòrëji TimedText TimedText talk Moduł Dyskusja modułu Wydarzenie Dyskusja wydarzenia Swòrë 0 4479 204454 67536 2026-07-05T14:46:05Z Terrus38 14026 Usunięto przekierowanie do [[Swòrnégace]] 204454 wikitext text/x-wiki {{Wies infobox | Wies=Swòrnégace | rodzôcz_wsë= | céch_wsë= | ôrt_gminë= | fana_wsë= | kôrta_wsë= | wòjewództwò=pòmòrsczé | kréz=chònicczi | gmina=Chònice | szôłtëstwò=Swòrnégace | miejskô= | wiechrzëzna= | gradëN=53 | minutëN=51 | sekùndëN=36 | gradëE=17 | minutëE=29 | sekùndëE=57 | wiżô= | rok=2008 | lëdztwò=555 | gãscëzna= | nr_czér=52 | pòcztowi_kòd=89-608 | reg_tôfla=GCH | SIMC= | szôłtës= | www= http://www.swornegacie.org/ | òdjimk= Poland_Swornegacie_-_church.jpg | galerëjô_commons= Category:Swornegacie |}} [[Òbrôzk:Swornegacie Kaszubski Dom Rękodzieła Ludowego 03.07.10 k.jpg|mały|left|Kaszëbsczé Dodóm Lëdowëch Rãcznëch Robòtów]] '''Swòrë''' (pò pòlskù ''Swornegacie'') – kaszëbskô wies pòłożonô w [[Pòmòrsczé wòjewództwò|pòmòrsczim wòjewództwie]], w [[Chònicczi kréz|chònicczim krézu]], w gminie Chònice. Wies je nad [[Kôrsyńsczé Jezoro|Kôrsyńsczim Jezorã]]. Papiéż Grzegòrz X pisôł ò ti wsë w 1272 rokù. Tu béł zôkón Augùstianów. W 1291 ròkù béł w ti wnożënie ksyżã [[Mscëwòj II]] z trójną prowadzëną. W Swòrnégacach w szkòle dzece ùczą sã [[Kaszëbsczi jãzëk|kaszëbsczégò jãzëka]]. == Lëteratura == * [http://www.ztn.com.pl/files_b/4_2009-lato-Ziemia%20Zaborska.pdf Przemysław Zientkowski, ''Swornegacie - stara kaszubska wieś'', „Ziemia Zaborska”, lato 2009, ss. 15 - 16.] == Bùtnowé lënczi == {{Commons}} * [http://dir.icm.edu.pl/pl/Slownik_geograficzny/Tom_XI/726 Swornigać w „Geògraficznym słowôrzu Pòlsczégò Królestwa i jinëch słowiańsczich krajów”] * [http://www.szkola-swornegacie.pl/ Szkòła] {{Kòntrola}} [[Kategòrëjô:Kaszëbë]] [[Kategòrëjô:Pòmòrsczé wsë]] [[Kategòrëjô:Gmina Chònice]] q56bz6gnvxe7v1397pidepgn6rt2otp 204456 204454 2026-07-05T14:49:06Z Terrus38 14026 kòrekta 204456 wikitext text/x-wiki {{Wies infobox | Wies=Swòrë | rodzôcz_wsë=Swòrów | céch_wsë= | ôrt_gminë= | fana_wsë= | kôrta_wsë= | wòjewództwò=pòmòrsczé | kréz=chònicczi | gmina=Chònice | szôłtëstwò=Swòrë | miejskô= | wiechrzëzna= | gradëN=53 | minutëN=51 | sekùndëN=36 | gradëE=17 | minutëE=29 | sekùndëE=57 | wiżô= | rok=2008 | lëdztwò=555 | gãscëzna= | nr_czér=52 | pòcztowi_kòd=89-608 | reg_tôfla=GCH | SIMC= | szôłtës= | www= http://www.swornegacie.org/ | òdjimk= Poland_Swornegacie_-_church.jpg | galerëjô_commons= Category:Swornegacie |}} [[Òbrôzk:Swornegacie Kaszubski Dom Rękodzieła Ludowego 03.07.10 k.jpg|mały|left|Kaszëbsczé Dodóm Lëdowëch Rãcznëch Robòtów]] '''Swòrë''' (pòl. ''Swornegacie'') – kaszëbskô wies pòłożonô w [[Pòmòrsczé wòjewództwò|pòmòrsczim wòjewództwie]], w [[Chònicczi kréz|chònicczim krézu]], w gminie Chònice. Leżi nad [[Kôrsyńsczé Jezoro|Kôrsyńsczim Jezorã]]. Wies òsta nadczidłô ju w 1272 rokù przez papieża Grégòra X. Tu dało zôkón [[Augùstianie|Augùstianów]]. W szkòle w Swòrach òdbiwają sã ùczbë [[Kaszëbsczi jãzëk|kaszëbsczégò jãzëka]]. == Lëteratura == * [http://www.ztn.com.pl/files_b/4_2009-lato-Ziemia%20Zaborska.pdf Przemysław Zientkowski, ''Swornegacie - stara kaszubska wieś'', „Ziemia Zaborska”, lato 2009, ss. 15 - 16.] == Bùtnowé lënczi == {{Commons}} * [http://dir.icm.edu.pl/pl/Slownik_geograficzny/Tom_XI/726 Swornigać w „Geògraficznym słowôrzu Pòlsczégò Królestwa i jinëch słowiańsczich krajów”] * [http://www.szkola-swornegacie.pl/ Szkòła] {{Kòntrola}} [[Kategòrëjô:Kaszëbë]] [[Kategòrëjô:Pòmòrsczé wsë]] [[Kategòrëjô:Gmina Chònice]] a2q1w482ujl33eaza3wmrbfpftsaxbc Lãbòrsczi kréz 0 4685 204512 199799 2026-07-06T11:57:21Z Iketsi 3254 [[pòlsczi jãzëk|pòl.]]→{{jãz.|pl}} 204512 wikitext text/x-wiki [[Òbrôzk:POL powiat lęborski COA.svg|mały|Herb krézu]] '''Lãbòrsczi kréz''' ({{jãz.|pl}} ''Powiat lęborski'') – je krezã w [[Pòmòrsczé wòjewództwò|pòmòrsczim wòjewództwie]] z sedzbą w [[Lãbòrg]]u.[[Norda|Norda.]] Wedle nôrodnégò spisënkù 2021, 1.438 lëdzy, w tim krézu, ùżiwô kaszëbsczégò jãzëka w dodomù. === Spòdlowé pòdôwczi === * Wiéchrzëzna: 706,99 km² * Lëdztwò: 63 606 ([[2004]]) == Historiô == W [[Môj|maju]] 1958 rokù Służba Bezpiekù Pòwiatowi Kòmańdë [[Òbëwatelskô Milicjô|Òbëwatelsczi Milicje]] w Lãbòrgù ùdbała so rozprôcowiwac kaszëbsczëch i ùkrajińsczëch dzejôrzów. == Bùtnowé lënczi == {{Commons}} * [http://www.powiat-lebork.com/ Starna przédnictwa krézu] {{Pòmòrsczé krézë}} {{Ùzémk artikla}} {{Kòntrola}} [[Kategòrëjô:Lãbòrsczi kréz| ]] 9eyjff489r3hr0g6jzy5x65thwi7snu Ecser 0 4813 204506 204417 2026-07-06T11:27:51Z Iketsi 3254 {{Ùzémk artikla}} {{Kòntrola}} 204506 wikitext text/x-wiki '''Ecser''' – [[wies]] w [[Madżarskô|Madżarsczi Repùblice]], pòłożóné w òbéńdze [[Bùdapeszt]]u, blëżnô pòrtu lotniczégò Ferihegy ë aùtodardżi M0 ë namòlnégò bana 120a (Budapest-Újszász-Szolnok). Bliscze jemù wsë są to Mglód, Vecsés, Gyömrő a Üllő. Lëdztwò gardu to 3272 mieszkańców. Wies mô baro dłëgô historëjô. Z 1315 rokù pòchôdô pierszô zmiónka ò wsë, leno Ecser bëł tam ju òd 896, czedë najachôł jen nôrod madżarsczi. == Galeriô == <center> <gallery> Òbrôzk:Ecseri 151.jpg| Òbrôzk:Ecseri 154.jpg| Òbrôzk:Széchenyi utca, kápolna, Ecseri 158.jpg| Òbrôzk:Ecseri 171.jpg| Òbrôzk:Szlovák Tájház, Ecseri 159.jpg| Òbrôzk:Ecseri 156.jpg| Òbrôzk:Ecseri 168.jpg| Òbrôzk:Ecseri 175.jpg| Òbrôzk:Ecseri 178.jpg| Òbrôzk:Ecseri 180.jpg| Òbrôzk:Ecseri 173.jpg| Òbrôzk:Széchenyi utca, Ecseri 166.jpg| </gallery> </center> == Bùtnowé lënczi == {{Commons}} {{Ùzémk artikla}} {{Kòntrola}} [[Kategòrëjô:Madżarskô]] o0brmj2y0w2ytz72as4y2ob1opc71m9 Swòrnégace 0 5160 204455 201126 2026-07-05T14:46:11Z Terrus38 14026 Przeczérowanié do [[Swòrë]] 204455 wikitext text/x-wiki #PATRZ [[Swòrë]] exxslxlgjbplu74sknz9uhm5frfw1l4 Barnim I 0 5925 204473 201216 2026-07-05T16:45:15Z HaxelKaszëba 17492 cos jem ùprawił ale czedës cos dodóm wicy kò za krótczi artikel 204473 wikitext text/x-wiki [[Òbrôzk:BarnimI.Siegel.JPG|mały|]] [[Òbrôzk:Cammin prince-bishopric 1250 (134037781).jpg|mały|Pòmòrsczé òkòlé kòl 1250 rokù]] '''Barnim I''' (ùr. kòl 1217 rokù) – béł princã [[Zôpadnô Pòmòrskô|Zôpadnégò Pòmòrzégò]] do 1278 rokù. Jegò matką bëła [[Mirosława]]. Miôł òn sztãpel z nôpisã: ''Sigillum Barnym illustris ducis Slauorum et Cassubie'' (sztãpel Barnima ... princa [[Słowiónie|Słowiónów]] a [[Kaszëbskô|Kaszëbsczi]])[https://books.google.pl/books?id=bq0OAAAAYAAJ&pg=RA1-PA141&lpg=RA1-PA141&dq=cassubien&source=bl&ots=aQIzj8B7AF&sig=6u1WG9v9vBBJFqcbfxFSBEWWtjo&hl=pl&sa=X&ved=0ahUKEwj7o7X3pcjLAhVlOJoKHaJXD58Q6AEIPzAG#v=onepage&q=cassubien&f=false]. Pòstawił téż [[Marijën Kòscół w Szczecënie]]. == Lëteratura == * [[Gerard Labùda]]: Kaszubi i ich dzieje, Gdańsk 1996, s. 90–91 ISBN 83-904950-9-0 (pò pòlskù) == Bùtnowé lënczi == {{Commons}} {{Ùzémk artikla}} {{Kòntrola}} [[Kategòrëjô:Ksyżëce Zôpadny Pòmòrsczi]] [[Kategòrëjô:Szczecëno‎]] fzwf17nsao09hddpwl29kbk9k1m6jmd Czerwònuszk 0 6153 204489 200361 2026-07-05T20:36:39Z Rabùszniczka 19710 Nót je wiãcy dopisac 204489 wikitext text/x-wiki [[Òbrôzk:Erithacus rubecula -RHS Garden Harlow Carr-8b-2c.jpg|mały|Czerwònuszk]] [[Òbrôzk:Erithacus rubecula whiterbyi Hartert, E, 1910 232 HdB Ouarsenis Algérie.jpg‎|mały|''Erithacus rubecula'']] [[Òbrôzk:Cuculus canorus canorus MHNT.ZOO.2010.11.150.1.jpg|mały|''Cuculus canorus'' + ''Erithacus rubecula'']] '''Czerwònuszk''' (''Erithacus rubecula'') — [[gatënk]] môłégò wanożnégò ptôcha z rodzëznë [[Muchòłówkaté|muchòłówka''tëch'']] ''(Muscicapidae), chòc wczesni zalëczóny do [[Drozdowaté|drozdowatëch]] (Turididae''). Jedurny ptôch w rodzaju ''Erithacus.'' Nie je zagrożóny. == Wëstãpòwanié == Czerwònuszk je do nalézeniô w całi Eùropie, zôpadnym parce Syberii i Azji Mniszi. Pòza tim są izolowóné pòpùlacje lągòwé w [[Algieriô|Algerii]], czë na òstrowach wschòdnégi [[Atlantik|Atlantikù]]. Ni ma gò na [[Islandëjô|Islandii]], w nordowi [[Skandinawiô|Skandinawii]] i w niechtërnëch partach [[Ruskô|Ruska]]. Zëmùje w pôłniowi i zôpadny Eùropie (pòlskô pòpùlacjô dërch na Iberijsczim Półòstrowie), północny Africe, Azji Mniszi i na Blësczim Wschòdze. W Pòlsce je to lëczny lągòwi ptôch, rozkòscérzóny w całim kraju. Pòdle szacënków [[Monitoringu Pospolitych Ptaków Lęgowyc|Monitoringu Pospolitych Ptaków Lęgowych]] w latach 2013-2018 pòpùlacjô pòlsczégò czerwònuszka rechòwała 2,20 - 2,98 milionów lągòwëch pôr<ref>Chodkiewicz T., Chylarecki P., Sikora A., Wardecki Ł., Bobrek R., Neubauer G., Marchowski D., Dmoch A., Kuczyński L.. [https://otop.org.pl/wp-content/uploads/2020/03/Biuletyn20_2019.pdf Raport z wdrażania art. 12 Dyrektywy Ptasiej w Polsce w latach 2013–2018: stan, zmiany, zagrożenia.] „Biuletyn Monitoringu Przyrody”. 20, s. 1–80, 2019.</ref>. Part ptôchów zëmùje na lągòwich terenach. == Znanczi gatënkù == Czerwònuszk je môłim ptôcha ò wëlecałi i wëprostowany, abò krągłi i przësadzësti sylwetce. Jegò cało mierzi 12,5-14 cm, rozcygłosc lecadłów wënosy 20-25 cm, a ògón mô 5-6 cm długòscy. Ptôch ważi leno 14-21 gramów. Òbie płce są szlachòwnô òpierzóné i są do se pòdobné rozmiarã. == Bùtnowé lënczi == {{Commons}} {{Ùzémk artikla}} {{Kòntrola}} [[Kategòrëjô:Ptôchë]] emk7r3yhqw5mmrirg0rylfed23yfwo4 204493 204489 2026-07-06T08:50:56Z Rabùszniczka 19710 Dopisa jem wiãcy, do korektë. Wcyg brëkùje infòboksu na gatënczi. 204493 wikitext text/x-wiki [[Òbrôzk:Erithacus rubecula -RHS Garden Harlow Carr-8b-2c.jpg|mały|Czerwònuszk]]'''Czerwònuszk''' (''Erithacus rubecula'') — [[gatënk]] môłégò wanożnégò ptôcha z rodzëznë [[Muchòłówkaté|muchòłówka''tëch'']] ''(Muscicapidae), chòc wczesni zalëczóny do [[Drozdowaté|drozdowatëch]] (Turididae''). Jedurny ptôch w rodzaju ''Erithacus.'' Nie je zagrożóny. == Systematika == Czerwònuszk razã z wieloma jinszëmi gatënkama òstôł fòrmalno òpsióny jak''ò Motacilla rubecula'' przez szwedzczégò rôdnika [[Karol Linneùsz|Karola Lineùsza]] w dzësąti edicji jegò przełomòwégò dlô biologii dokôzu z 1758 - ''[[Systema Naturae]].''<ref>[[Karol Linneùsz]]: [https://www.biodiversitylibrary.org/item/10277#page/208/mode/1up ''Systema naturae per regna tria naturae, secundum classes, ordines, genera, species, cum characteribus, differentiis, synonymis, locis. Tomus I. Editio decima, reformata.''] Holmiae. (Laurentii Salvii), 1758, s. 188. (łac.).</ref> == Wëstãpòwanié == [[Òbrôzk:ErithacusRubeculaIUCN.svg|mały|Kôrta wëstãpòwaniô czerwònuszka. Jasnozelony - lągòwi cząd, cemnozelony - całorocznô pòpùlacjô, mòdri - zëmòwiszcza, różowi - introdukòwóny i prôwdopòdobno wëmarłi.]] Czerwònuszk je do nalézeniô w całi Eùropie, zôpadnym parce Syberii i Azji Mniszi. Pòza tim są izolowóné pòpùlacje lągòwé w [[Algieriô|Algerii]], czë na òstrowach wschòdnégi [[Atlantik|Atlantikù]]. Ni ma gò na [[Islandëjô|Islandii]], w nordowi [[Skandinawiô|Skandinawii]] i w niechtërnëch partach [[Ruskô|Ruska]]. Zëmùje w pôłniowi i zôpadny Eùropie (pòlskô pòpùlacjô dërch na Iberijsczim Półòstrowie), północny Africe, Azji Mniszi i na Blësczim Wschòdze. W Pòlsce je to lëczny lągòwi ptôch, rozkòscérzóny w całim kraju. Pòdle szacënków [[Monitoringu Pospolitych Ptaków Lęgowyc|Monitoringu Pospolitych Ptaków Lęgowych]] w latach 2013-2018 pòpùlacjô pòlsczégò czerwònuszka rechòwała 2,20 - 2,98 milionów lągòwëch pôr<ref>Chodkiewicz T., Chylarecki P., Sikora A., Wardecki Ł., Bobrek R., Neubauer G., Marchowski D., Dmoch A., Kuczyński L.. [https://otop.org.pl/wp-content/uploads/2020/03/Biuletyn20_2019.pdf Raport z wdrażania art. 12 Dyrektywy Ptasiej w Polsce w latach 2013–2018: stan, zmiany, zagrożenia.] „Biuletyn Monitoringu Przyrody”. 20, s. 1–80, 2019.</ref>. Part ptôchów zëmùje na lągòwich terenach. == Znanczi gatënkù == [[Òbrôzk:Bold Robin (98857441).jpeg|mały|Charakteristicznô plama na piersë czerwònuszka.]] === Òpisenk pòzdrzatkù === Czerwònuszk je môłim ptôcha ò wëlecałi i wëprostowany, abò krągłi i przësadzësti sylwetce. Jegò cało mierzi 12,5-14 cm, rozcygłosc lecadłów wënosy 20-25 cm, a ògón mô 5-6 cm długòscy. Ptôch ważi leno 14-21 gramów. Òbie płce są szlachòwnô òpierzóné i są do se pòdobné rozmiarã. Czerwònuszczi mają charakteristiczné ùfarwienié - jakò dozdrzeniałé òsobniczi mają dërdzawą plamã, jakô rozcygô sã na piersë, gôrdzel i bòczi głowë, kòl ni je mòdrô-szari "kòntur". Plama ta z czasã widnie i staje sã matowô. Brzëch ptôcha je biôłi, a jegò bòczi, ògón i slôdk są szaro-brùné. Lëcadła są òliwkòwò-zeloné. [[Òbrôzk:European Robin - Erithacus rubecula - Juvenile.jpg|mały|Czerwònuszk w juwelanym ùfarwieniu.]] Młodé ptôchë w juwenalnym òpierzeniu są bruné z cëmnyma plamami i jasnijszim brzëchã. Dozdrzeniałé ùfarwienié zaczinô pòjawiac sã pò kòl półtora miesąca zëcô (czerwińc - séwnik), a pò rokù òsygô finalną fòrmã. <ref>Rob Hume i inni red., ''Ptaki Europy: przewodnik do rozpoznawania'', Warszawa: Multico Oficyna Wydawnicza, 2023, s. 392, [[:pl:Specjalna:Książki/9788377636084|ISBN 978-83-7763-608-4.]]</ref> Dzãka dërdzawi plamie i jednorodny farwie lëcadłow czerwònusczi są dosc łatwé do rozpòznaniô w rôdnym strzôdwiszczu. [[Òbrôzk:Erithacus rubecula whiterbyi Hartert, E, 1910 232 HdB Ouarsenis Algérie.jpg‎|mały|''Erithacus rubecula'']] == Lągòwi cząd == === Jaje === Samica niese 5-6<ref>Michał Radziszewski i inni red., Ilustrowana encyklopedia ptaków Polski: przewodnik ornitologa, Bełchatów : Warszawa: Fenix ; PZWL Wydawnictwo Lekarskie Sp. z o.o., 2022, s. 264, [[:pl:Specjalna:Książki/9788365808486|ISBN 978-83-65808-48-6.]]</ref> jajów ò różnym ùfarwieniu (nôczãscy są to biôłe jaje z dërdzawëmi, czë zelonymi kropkama). W drëdżim lągù je jich mni - 4-5 sztëk. Téż nie kòżdô samica przëstãpùje do drëdżégò lągù. == Bùtnowé lënczi == {{Commons}} {{Ùzémk artikla}} {{Kòntrola}} [[Kategòrëjô:Ptôchë]] <references /> [[Kategòrëjô:Mùchòłówczi]] okixd854iqeajiqb2lcyvyc8fssy6el 204494 204493 2026-07-06T09:32:10Z Orbitminis 18250 204494 wikitext text/x-wiki [[Òbrôzk:Erithacus rubecula -RHS Garden Harlow Carr-8b-2c.jpg|mały|Czerwònuszk]]'''Czerwònuszk''' (''Erithacus rubecula'') — [[gatënk]] môłégò wanożnégò ptôcha z rodzëznë [[Muchòłówkaté|muchòłówka''tëch'']] ''(Muscicapidae), chòc wczesni zalëczóny do [[Drozdowaté|drozdowatëch]] (Turididae''). Jedurny ptôch w rodzaju ''Erithacus.'' Nie je zagrożóny. == Systematika == Czerwònuszk razã z wieloma jinszëmi gatënkama òstôł fòrmalno òpsióny jak''ò Motacilla rubecula'' przez szwedzczégò rôdnika [[Karol Linneùsz|Karola Lineùsza]] w dzësąti edicji jegò przełomòwégò dlô biologii dokôzu z 1758 - ''[[Systema Naturae]].''<ref>[[Karol Linneùsz]]: [https://www.biodiversitylibrary.org/item/10277#page/208/mode/1up ''Systema naturae per regna tria naturae, secundum classes, ordines, genera, species, cum characteribus, differentiis, synonymis, locis. Tomus I. Editio decima, reformata.''] Holmiae. (Laurentii Salvii), 1758, s. 188. (łac.).</ref> == Wëstãpòwanié == [[Òbrôzk:ErithacusRubeculaIUCN.svg|mały|Kôrta wëstãpòwaniô czerwònuszka. Jasnozelony - lągòwi cząd, cemnozelony - całorocznô pòpùlacjô, mòdri - zëmòwiszcza, różowi - introdukòwóny i prôwdopòdobno wëmarłi.]] Czerwònuszk je do nalézeniô w całi Eùropie, zôpadnym parce Syberii i Azji Mniszi. Pòza tim są izolowóné pòpùlacje lągòwé w [[Algieriô|Algerii]], czë na òstrowach wschòdnégi [[Atlantik|Atlantikù]]. Ni ma gò na [[Islandëjô|Islandii]], w nordowi [[Skandinawiô|Skandinawii]] i w niechtërnëch partach [[Ruskô|Ruska]]. Zëmùje w pôłniowi i zôpadny Eùropie (pòlskô pòpùlacjô dërch na Iberijsczim Półòstrowie), północny Africe, Azji Mniszi i na Blësczim Wschòdze. W Pòlsce je to lëczny lągòwi ptôch, rozkòscérzóny w całim kraju. Pòdle szacënków [[Monitoringu Pospolitych Ptaków Lęgowyc|Monitoringu Pospolitych Ptaków Lęgowych]] w latach 2013-2018 pòpùlacjô pòlsczégò czerwònuszka rechòwała 2,20 - 2,98 milionów lągòwëch pôr<ref>Chodkiewicz T., Chylarecki P., Sikora A., Wardecki Ł., Bobrek R., Neubauer G., Marchowski D., Dmoch A., Kuczyński L.. [https://otop.org.pl/wp-content/uploads/2020/03/Biuletyn20_2019.pdf Raport z wdrażania art. 12 Dyrektywy Ptasiej w Polsce w latach 2013–2018: stan, zmiany, zagrożenia.] „Biuletyn Monitoringu Przyrody”. 20, s. 1–80, 2019.</ref>. Part ptôchów zëmùje na lągòwich terenach. == Znanczi gatënkù == [[Òbrôzk:Bold Robin (98857441).jpeg|mały|Charakteristicznô plama na piersë czerwònuszka.]] === Òpisenk pòzdrzatkù === Czerwònuszk je môłim ptôcha ò wëlecałi i wëprostowany, abò krągłi i przësadzësti sylwetce. Jegò cało mierzi 12,5-14 cm, rozcygłosc lecadłów wënosy 20-25 cm, a ògón mô 5-6 cm długòscy. Ptôch ważi leno 14-21 gramów. Òbie płce są szlachòwnô òpierzóné i są do se pòdobné rozmiarã. Czerwònuszczi mają charakteristiczné ùfarwienié - jakò dozdrzeniałé òsobniczi mają dërdzawą plamã, jakô rozcygô sã na piersë, gôrdzel i bòczi głowë, kòl ni je mòdrô-szari "kòntur". Plama ta z czasã widnie i staje sã matowô. Brzëch ptôcha je biôłi, a jegò bòczi, ògón i slôdk są szaro-brùné. Lëcadła są òliwkòwò-zeloné. [[Òbrôzk:European Robin - Erithacus rubecula - Juvenile.jpg|mały|Czerwònuszk w juwelanym ùfarwieniu.]] Młodé ptôchë w juwenalnym òpierzeniu są bruné z cëmnyma plamami i jasnijszim brzëchã. Dozdrzeniałé ùfarwienié zaczinô pòjawiac sã pò kòl półtora miesąca zëcô (czerwińc - séwnik), a pò rokù òsygô finalną fòrmã. <ref>Rob Hume i inni red., ''Ptaki Europy: przewodnik do rozpoznawania'', Warszawa: Multico Oficyna Wydawnicza, 2023, s. 392, [[:pl:Specjalna:Książki/9788377636084|ISBN 978-83-7763-608-4.]]</ref> Dzãka dërdzawi plamie i jednorodny farwie lëcadłow czerwònusczi są dosc łatwé do rozpòznaniô w rôdnym strzôdwiszczu. [[Òbrôzk:Erithacus rubecula whiterbyi Hartert, E, 1910 232 HdB Ouarsenis Algérie.jpg‎|mały|''Erithacus rubecula'']] == Lągòwi cząd == === Jaje === Samica niese 5-6<ref>Michał Radziszewski i inni red., Ilustrowana encyklopedia ptaków Polski: przewodnik ornitologa, Bełchatów : Warszawa: Fenix ; PZWL Wydawnictwo Lekarskie Sp. z o.o., 2022, s. 264, [[:pl:Specjalna:Książki/9788365808486|ISBN 978-83-65808-48-6.]]</ref> jajów ò różnym ùfarwieniu (nôczãscy są to biôłe jaje z dërdzawëmi, czë zelonymi kropkama). W drëdżim lągù je jich mni - 4-5 sztëk. Téż nie kòżdô samica przëstãpùje do drëdżégò lągù. == Bùtnowé lënczi == {{Commons}} == Przëpisë == [[Kategòrëjô:Ptôchë]] <references />{{Ùzémk artikla}} {{Kòntrola}} [[Kategòrëjô:Mùchòłówczi]] cfxsx0g9ndvexzhl06oesrwv34se42u 204495 204494 2026-07-06T10:30:37Z Terrus38 14026 kòrekta 204495 wikitext text/x-wiki [[Òbrôzk:Erithacus rubecula -RHS Garden Harlow Carr-8b-2c.jpg|mały|Czerwònuszk]]'''Czerwònuszk''' (''Erithacus rubecula'') — [[gatënk]] môłégò wanożnégò ptôcha z rodzëznë [[Muchòłówkòwaté|muchòłówkòwatëch]] ''(Muscicapidae), chòc wczasni zaliczóny do [[Drozdowaté|drozdowatëch]] (Turididae''). Jedurny ptôch w rodzaju ''Erithacus.'' Nie je zagrożony. == Systematika == Czerwònuszk razã z wiele jinszima gatënkama òstôł fòrmalno òpisóny jakò ''Motacilla rubecula'' przez szwedzczégò przërodnika [[Karol Linneùsz|Karola Linneùsza]] w dzësąti edicji jegò przełomòwégò dlô biologii dokazu z 1758 – ''[[Systema Naturae]].''<ref>[[Karol Linneùsz]]: [https://www.biodiversitylibrary.org/item/10277#page/208/mode/1up ''Systema naturae per regna tria naturae, secundum classes, ordines, genera, species, cum characteribus, differentiis, synonymis, locis. Tomus I. Editio decima, reformata.''] Holmiae. (Laurentii Salvii), 1758, s. 188. (łac.).</ref> == Wëstãpòwanié == [[Òbrôzk:ErithacusRubeculaIUCN.svg|mały|Kôrta wëstãpòwaniô czerwònuszka. Jasnozelony – lãgòwi cząd, cemnozelony – całorocznô pòpùlacjô, mòdri – zëmòwiszcza, różewi – introdukòwóny i prôwdopòdobno wëmarłi.]] Czerwònuszk je do nalézeniô wnet w całi [[Eùropa|Eùropie]], zôpadnym parce [[Syberiô|Syberii]] i Azji Mniészi. Pòza tim są izolowóné lãgòwé pòpùlacje w [[Algieriô|Algerii]] czë na òstrowach wschódnégò [[Atlantik|Atlantikù]]. Ni ma gò na [[Islandëjô|Islandii]], w nordowi [[Skandinawiô|Skandinawii]] i w niechtërnëch partach [[Ruskô|Rusczi]]. Zëmùje w pôłniowi i zôpadny Eùropie (pòlskô pòpùlacjô na Iberijsczim Półòstrowie), północny Africe, Azji Mniészi i na Blësczim Wschòdze. W Pòlsce je to lëczny lãgòwi ptôch, rozkòscérzóny w całim kraju. Pòdle szacënków ''Monitoringu Pospolitych Ptaków Lęgowych'', w latach 2013-2018 pòpùlacjô pòlsczégò czerwònuszka rechòwała 2,20–2,98 milionów lãgòwëch pôr<ref>Chodkiewicz T., Chylarecki P., Sikora A., Wardecki Ł., Bobrek R., Neubauer G., Marchowski D., Dmoch A., Kuczyński L.. [https://otop.org.pl/wp-content/uploads/2020/03/Biuletyn20_2019.pdf Raport z wdrażania art. 12 Dyrektywy Ptasiej w Polsce w latach 2013–2018: stan, zmiany, zagrożenia.] „Biuletyn Monitoringu Przyrody”. 20, s. 1–80, 2019.</ref>. Dzél ptôchów zëmùje na lãgòwëch terenach. == Znanczi gatënkù == [[Òbrôzk:Bold Robin (98857441).jpeg|mały|Charakteristicznô plama na piersë czerwònuszka.]] === Òpisënk wëzdrzatkù === Czerwònuszk je môłim ptôchã ò wëlecałi i wëprostowany, abò krãgłi i przësadzësti sylwetce. Jegò cało mierzi 12,5–14 cm, rozcygłosc lecëdłów wënosy 20-25 cm, a ògón mô 5-6 cm długòscy. Ptôch wôżi leno 14-21 gramów. Òbie pëłce są szlachòwno òpierzóné i są do se pòdobné rozmiarã. Czerwònuszczi mają charakteristiczné ùfarwienié - jakò dozdrzeniałé òsobniczi mają dërdzawą plamã, jakô rozcygô sã na piersë, gôrdzel i bòczi głowë, kòl ni je mòdro-szari "kòntur". Na plama z czasã widnieje i staje sã matowô. Brzëch ptôcha je biôłi, a jegò bòczi, ògón i slôdk są szaro-bruné. Lecëdła są òliwkòwò-zeloné. [[Òbrôzk:European Robin - Erithacus rubecula - Juvenile.jpg|mały|Czerwònuszk w juwenalnym ùfarwieniu.]] Młodé ptôchë w juwenalnym òpierzeniu są bruné z cemnyma plamama i jasniészim brzëchã. Dozdrzeniałé ùfarwienié zaczinô pòjawiac sã pò kòl półtora miesąca zëcô (czerwińc – séwnik), a pò rokù òsygô finalną fòrmã. <ref>Rob Hume i inni red., ''Ptaki Europy: przewodnik do rozpoznawania'', Warszawa: Multico Oficyna Wydawnicza, 2023, s. 392, [[:pl:Specjalna:Książki/9788377636084|ISBN 978-83-7763-608-4.]]</ref> Dzãka dërdzawi plamie i jednorodny farwie lecëdłów czerwònusczi są dosc łôtwé do rozpòznaniô w nôtërnym strzodowiszczu. [[Òbrôzk:Erithacus rubecula whiterbyi Hartert, E, 1910 232 HdB Ouarsenis Algérie.jpg‎|mały|''Erithacus rubecula'']] == Lãgòwi cząd == === Jaja === Samica niese 5-6<ref>Michał Radziszewski i inni red., Ilustrowana encyklopedia ptaków Polski: przewodnik ornitologa, Bełchatów : Warszawa: Fenix ; PZWL Wydawnictwo Lekarskie Sp. z o.o., 2022, s. 264, [[:pl:Specjalna:Książki/9788365808486|ISBN 978-83-65808-48-6.]]</ref> jajów ò różnym ùfarwieniu (nôczãscy są to biôłé jaja z dërdzawima abò zelonyma kropkama). W drëdżim lãgù je jich mni – òd 4 do 5 sztëk. Nié kòżdô samica przëstãpùje do drëdżégò lãgù. == Bùtnowé lënczi == {{Commons}} == Przëpisë == [[Kategòrëjô:Ptôchë]] <references />{{Ùzémk artikla}} {{Kòntrola}} [[Kategòrëjô:Mùchòłówczi]] kj5qb8vx69ojyqkwbh25hvhcke787et 204500 204495 2026-07-06T11:19:23Z Iketsi 3254 Przëpisë 204500 wikitext text/x-wiki [[Òbrôzk:Erithacus rubecula -RHS Garden Harlow Carr-8b-2c.jpg|mały|Czerwònuszk]] '''Czerwònuszk''' (''Erithacus rubecula'') — [[gatënk]] môłégò wanożnégò ptôcha z rodzëznë [[Muchòłówkòwaté|muchòłówkòwatëch]] (''Muscicapidae''), chòc wczasni zaliczóny do [[Drozdowaté|drozdowatëch]] (''Turididae''). Jedurny ptôch w rodzaju ''Erithacus.'' Nie je zagrożony. == Systematika == Czerwònuszk razã z wiele jinszima gatënkama òstôł fòrmalno òpisóny jakò ''Motacilla rubecula'' przez szwedzczégò przërodnika [[Karol Linneùsz|Karola Linneùsza]] w dzësąti edicji jegò przełomòwégò dlô biologii dokazu z 1758 – ''[[Systema Naturae]].''<ref>[[Karol Linneùsz]]: [https://www.biodiversitylibrary.org/item/10277#page/208/mode/1up ''Systema naturae per regna tria naturae, secundum classes, ordines, genera, species, cum characteribus, differentiis, synonymis, locis. Tomus I. Editio decima, reformata.''] Holmiae. (Laurentii Salvii), 1758, s. 188. (łac.).</ref> == Wëstãpòwanié == [[Òbrôzk:ErithacusRubeculaIUCN.svg|mały|Kôrta wëstãpòwaniô czerwònuszka. Jasnozelony – lãgòwi cząd, cemnozelony – całorocznô pòpùlacjô, mòdri – zëmòwiszcza, różewi – introdukòwóny i prôwdopòdobno wëmarłi.]] Czerwònuszk je do nalézeniô wnet w całi [[Eùropa|Eùropie]], zôpadnym parce [[Syberiô|Syberii]] i Azji Mniészi. Pòza tim są izolowóné lãgòwé pòpùlacje w [[Algieriô|Algerii]] czë na òstrowach wschódnégò [[Atlantik|Atlantikù]]. Ni ma gò na [[Islandëjô|Islandii]], w nordowi [[Skandinawiô|Skandinawii]] i w niechtërnëch partach [[Ruskô|Rusczi]]. Zëmùje w pôłniowi i zôpadny Eùropie (pòlskô pòpùlacjô na Iberijsczim Półòstrowie), północny Africe, Azji Mniészi i na Blësczim Wschòdze. W Pòlsce je to lëczny lãgòwi ptôch, rozkòscérzóny w całim kraju. Pòdle szacënków ''Monitoringu Pospolitych Ptaków Lęgowych'', w latach 2013-2018 pòpùlacjô pòlsczégò czerwònuszka rechòwała 2,20–2,98 milionów lãgòwëch pôr<ref>Chodkiewicz T., Chylarecki P., Sikora A., Wardecki Ł., Bobrek R., Neubauer G., Marchowski D., Dmoch A., Kuczyński L.. [https://otop.org.pl/wp-content/uploads/2020/03/Biuletyn20_2019.pdf Raport z wdrażania art. 12 Dyrektywy Ptasiej w Polsce w latach 2013–2018: stan, zmiany, zagrożenia.] „Biuletyn Monitoringu Przyrody”. 20, s. 1–80, 2019.</ref>. Dzél ptôchów zëmùje na lãgòwëch terenach. == Znanczi gatënkù == [[Òbrôzk:Bold Robin (98857441).jpeg|mały|Charakteristicznô plama na piersë czerwònuszka.]] === Òpisënk wëzdrzatkù === Czerwònuszk je môłim ptôchã ò wëlecałi i wëprostowany, abò krãgłi i przësadzësti sylwetce. Jegò cało mierzi 12,5–14 cm, rozcygłosc lecëdłów wënosy 20-25 cm, a ògón mô 5-6 cm długòscy. Ptôch wôżi leno 14-21 gramów. Òbie pëłce są szlachòwno òpierzóné i są do se pòdobné rozmiarã. Czerwònuszczi mają charakteristiczné ùfarwienié - jakò dozdrzeniałé òsobniczi mają dërdzawą plamã, jakô rozcygô sã na piersë, gôrdzel i bòczi głowë, kòl ni je mòdro-szari "kòntur". Na plama z czasã widnieje i staje sã matowô. Brzëch ptôcha je biôłi, a jegò bòczi, ògón i slôdk są szaro-bruné. Lecëdła są òliwkòwò-zeloné. [[Òbrôzk:European Robin - Erithacus rubecula - Juvenile.jpg|mały|Czerwònuszk w juwenalnym ùfarwieniu.]] Młodé ptôchë w juwenalnym òpierzeniu są bruné z cemnyma plamama i jasniészim brzëchã. Dozdrzeniałé ùfarwienié zaczinô pòjawiac sã pò kòl półtora miesąca zëcô (czerwińc – séwnik), a pò rokù òsygô finalną fòrmã. <ref>Rob Hume i inni red., ''Ptaki Europy: przewodnik do rozpoznawania'', Warszawa: Multico Oficyna Wydawnicza, 2023, s. 392, [[:pl:Specjalna:Książki/9788377636084|ISBN 978-83-7763-608-4.]]</ref> Dzãka dërdzawi plamie i jednorodny farwie lecëdłów czerwònusczi są dosc łôtwé do rozpòznaniô w nôtërnym strzodowiszczu. [[Òbrôzk:Erithacus rubecula whiterbyi Hartert, E, 1910 232 HdB Ouarsenis Algérie.jpg‎|mały|''Erithacus rubecula'']] == Lãgòwi cząd == === Jaja === Samica niese 5-6<ref>Michał Radziszewski i inni red., Ilustrowana encyklopedia ptaków Polski: przewodnik ornitologa, Bełchatów : Warszawa: Fenix ; PZWL Wydawnictwo Lekarskie Sp. z o.o., 2022, s. 264, [[:pl:Specjalna:Książki/9788365808486|ISBN 978-83-65808-48-6.]]</ref> jajów ò różnym ùfarwieniu (nôczãscy są to biôłé jaja z dërdzawima abò zelonyma kropkama). W drëdżim lãgù je jich mni – òd 4 do 5 sztëk. Nié kòżdô samica przëstãpùje do drëdżégò lãgù. == Bùtnowé lënczi == {{Commons}} == Przëpisë == {{Przëpisë}} {{Ùzémk artikla}} {{Kòntrola}} [[Kategòrëjô:Ptôchë]] [[Kategòrëjô:Mùchòłówczi]] cvfyou4w6grzdk3izd27bqnvek68jgc Złotowò 0 6853 204507 193896 2026-07-06T11:29:12Z Terrus38 14026 kòrekta 204507 wikitext text/x-wiki {{gard-infobox | Gard = Złotowò | adjektiw_mask = złotowsczi | adjektiw_fem = złotowskô | céch = POL Złotów COA.svg | fana = POL Złotów flag.svg | karta = | wòjewództwò = wiôlgòpòlsczé | kréz = złotowsczi kréz | grodzki = | wiéchrzëzna = 11,58 | stopniN = 53 | minutN = 21 | stopniE = 17 | minutE = 02 | rok = 2013 | lëdztwò = 18 712 | czerënkòwi numer = (+48) 67 | pòcztowi kòd = 77-400 | registracëjné tôfle = PZL | bùrméster = Jakub Pieniążkowski | adres_um = Urząd Miasta Złotowa, al. Piasta 1 | kod_poczt_um = 77-400 Złotów | tel_um = +48 67 263 26 40 | fax_um = +48 67 265 00 25 | mail_um = urzad@zlotow.pl | www = http://zlotow.pl |gwôsné miono=Złotów|dopełniacz=Złotowa}} '''Złotowò''' (we zdrojach: ''Vulatovum'' 1370, ''Welatowo'', ''Zlothaue'', ''Majus Zlothkowo'' XV w., ''Slothowo'' 1491, ''Plotho'' XVIII w., ''Flatow'', ''Złotowo''; ''Złetowo'' (Cenôwa); [[Pòlsczi jãzëk|pòl.]] ''Złotów'', [[Miemiecczi jãzëk|miem.]] ''Flatow'') je [[kréz]]ewim [[gard|gardã]] we [[wiôlgòpòlsczé wòjewództwò|wiôlgòpòlsczim wòjewództwie]] nad [[Głomia|Głomią]] ë piãc jezorama. Je jednym òd nôwikszich gardów [[Krajnô|Krôjnë]]. W 2013 rokù miało 18 712 mieszkańców, a jegò pòwiéchrzniô wënosy 11,58 km<sup>2</sup>. Òd [[1772]] ([[pierszi rozbiér Pòlsczi]]) do [[1945]] (kùńc [[drëgô swiatowô wòjna|drëdżi swiatowi wòjnë]]) słëchôł do [[Miemieckô|Miemiecczi]], dze béł wôżnym òstrzódkã [[Pòlaczi|pòlsczi miészëznë]]. W latach [[1938]]-[[1945|45]] leżôł w [[Pòmrë|Pòmrach]], [[1945]]-[[1946|46]] w [[Bëdgòsczé wòjewództwò (1945-1946)|bëdgòsczim wòjewództwie]], w [[1946]]-[[1950|50]] w [[Szczecyńsczé wòjewództwò (1946-1950)|szczecyńsczim]], w [[1950]]-[[1975|75]] w [[Kòszalińsczé wòjewództwò (1950-1975)|kòszalińsczim]], w [[1975]]-[[1998|98]] w [[Pilsczé wòjewództwò (1975-1998)|pilsczim]]. Złotowò leżi nad jezorama: [[Złotowsczé Jezoro|Złotowsczim]] (Miesczim), [[Jezoro Baba|Babą]], Bùrméstrowsczim, Probòszczowsczim, [[Zalesczé Jezoro|Zalesczim]] ë rzéką [[Głomia|Głomią]], we westrzédnozôpadnym parce Krôjnë. {{stub}} [[Kategòrëjô:Pòmòrsczé gardë]] [[Kategòrëjô:Gardë w Pòlsce]] [[Kategòrëjô:Wiôlgòpòlsczé wòjewództwò]] 6acjtepdfxvb9t97j087snwubx46x61 Lila Downs 0 7143 204511 189385 2026-07-06T11:56:34Z Iketsi 3254 kat. 204511 wikitext text/x-wiki [[Òbrôzk:Lila Downs in Istanbul.jpg|mały|Lila Downs]] '''Ana Lila Downs Sánchez''' (ùr. [[9 séwnika]] [[1968]], [[Oakzàca]], [[Meksyk]]) je Meksykanką i spiéwôrką. {{Ùzémk artikla}} {{Kòntrola}} {{SORTUJ:Downs, Lila}} [[Kategòrëjô:Spiéwôrczi]] [[Kategòrëjô:Meksyk]] 56esdcq0067o30i0ahzvtxlljq1ax5s Zeleniô 0 8446 204475 198303 2026-07-05T18:58:19Z Terrus38 14026 204475 wikitext text/x-wiki {{WYŚWIETLANYTYTUŁ:Zeleniô}} '''Zeleniô''' – [[Kaszëbë|kaszëbskô]] wies w [[gmina Kòscérzna|gminie Kòscérzna]], w [[Kòscersczi kréz|kòscersczim krézu]] [[Pòmòrsczé wòjewództwò|pòmòrsczégò wòjewództwa]]. Òd 1890 rokù tu bëła dwalatnô Gbùrskô Szkòła. == Bùtnowé lënczi == {{Commons}} * [http://dir.icm.edu.pl/pl/Slownik_geograficzny/Tom_XIV/593 ''Zielenina'' w „Geògraficznym słowôrzu Pòlsczégò Królestwa i jinëch słowiańsczich krajów”] [[Kategòrëjô:Gmina Kòscérzna]] [[Kategòrëjô:Kaszëbë]] h3f37lrqzc23dhdqiqpkex3fmlk1cjo 204476 204475 2026-07-05T18:59:36Z Terrus38 14026 Terrus38 przeniós starnã [[Zelenin]] do [[Zeleniô]]: Błędna pisownia tytułu: Zeleniô je zgòdą z http://skarbnicakaszubska.pl/uchwaa-nr-6-2010/ 204475 wikitext text/x-wiki {{WYŚWIETLANYTYTUŁ:Zeleniô}} '''Zeleniô''' – [[Kaszëbë|kaszëbskô]] wies w [[gmina Kòscérzna|gminie Kòscérzna]], w [[Kòscersczi kréz|kòscersczim krézu]] [[Pòmòrsczé wòjewództwò|pòmòrsczégò wòjewództwa]]. Òd 1890 rokù tu bëła dwalatnô Gbùrskô Szkòła. == Bùtnowé lënczi == {{Commons}} * [http://dir.icm.edu.pl/pl/Slownik_geograficzny/Tom_XIV/593 ''Zielenina'' w „Geògraficznym słowôrzu Pòlsczégò Królestwa i jinëch słowiańsczich krajów”] [[Kategòrëjô:Gmina Kòscérzna]] [[Kategòrëjô:Kaszëbë]] h3f37lrqzc23dhdqiqpkex3fmlk1cjo 204479 204476 2026-07-05T19:15:43Z Terrus38 14026 rozwicé artikla 204479 wikitext text/x-wiki {{WYŚWIETLANYTYTUŁ:Zeleniô}}{{Wies infobox|Wies=Zeleniô|rodzôcz_wsë=Zelenie|wòjewództwò=pòmòrsczé|kréz=kòscérsczi|gmina=Kòscérzna|szôłtëstwò=Zeleniô|lëdztwò=196|rok=2021|pòcztowi_kòd=83-422}} '''Zeleniô''' (pòl. ''Zielenin'', niem. ''Zelenin'', òb lata 1942-1945 ''Altehütte'')<ref>wëbiér zdrzódłów, M. Kubicki ''Germanizacja nazw miejscowości w okręgu Rzeszy Gdańsk-Prusy Zachodnie 1939-1945'' (pòl.), Instytut Pamięci Narodowej, Gduńsk-Warszawa, 2022</ref> – [[Kaszëbë|kaszëbskô]] wies leżącô na [[Kaszëbsczé Pòjezerzé|Kaszëbsczim Pòjezerzim]], nad rzéką [[Wierzëca|Wierzëcą]] w [[Pòmòrsczé wòjewództwò|pòmòrsczim wòjewództwie]], w [[Kòscérsczi kréz|kòscérsczim krézu]], w [[Gmina Kòscérzna|gminie Kòscérzna]]. Wies je sedzbą szôłtëstwa Zeleniô. W 2021 rokù w Zelenie mieszkało 196 òsób.<ref>Polska w Liczbach, ''[https://www.polskawliczbach.pl/wies_Zielenin Wieś Zielenin w liczbach]'' (pòl.), przëstãp z 5.07.2026 r.</ref> W 1890 rokù pòwsta tuwò dwalatnô Gbùrskô Szkòła. == Bùtnowé lënczi == {{Commons}} * [http://dir.icm.edu.pl/pl/Slownik_geograficzny/Tom_XIV/593 ''Zielenina'' w „Geògraficznym słowôrzu Pòlsczégò Królestwa i jinëch słowiańsczich krajów”] [[Kategòrëjô:Gmina Kòscérzna]] [[Kategòrëjô:Kaszëbë]] 9azju7yc52znmavtk6lf6v0d3n6du4s 204480 204479 2026-07-05T19:15:59Z Terrus38 14026 204480 wikitext text/x-wiki {{WYŚWIETLANYTYTUŁ:Zeleniô}}{{Wies infobox|Wies=Zeleniô|rodzôcz_wsë=Zelenie|wòjewództwò=pòmòrsczé|kréz=kòscérsczi|gmina=Kòscérzna|szôłtëstwò=Zeleniô|lëdztwò=196|rok=2021|pòcztowi_kòd=83-422}} '''Zeleniô''' (pòl. ''Zielenin'', niem. ''Zelenin'', òb lata 1942-1945 ''Altehütte'')<ref>wëbiér zdrzódłów, M. Kubicki ''Germanizacja nazw miejscowości w okręgu Rzeszy Gdańsk-Prusy Zachodnie 1939-1945'' (pòl.), Instytut Pamięci Narodowej, Gduńsk-Warszawa, 2022</ref> – [[Kaszëbë|kaszëbskô]] wies leżącô na [[Kaszëbsczé Pòjezerzé|Kaszëbsczim Pòjezerzim]], nad rzéką [[Wierzëca|Wierzëcą]] w [[Pòmòrsczé wòjewództwò|pòmòrsczim wòjewództwie]], w [[Kòscérsczi kréz|kòscérsczim krézu]], w [[Gmina Kòscérzna|gminie Kòscérzna]]. Wies je sedzbą szôłtëstwa Zeleniô. W 2021 rokù w Zelenie mieszkało 196 òsób.<ref>Polska w Liczbach, ''[https://www.polskawliczbach.pl/wies_Zielenin Wieś Zielenin w liczbach]'' (pòl.), przëstãp z 5.07.2026 r.</ref> W 1890 rokù pòwsta tuwò dwalatnô Gbùrskô Szkòła. == Bùtnowé lënczi == {{Commons}} == Przëpisë == * [http://dir.icm.edu.pl/pl/Slownik_geograficzny/Tom_XIV/593 ''Zielenina'' w „Geògraficznym słowôrzu Pòlsczégò Królestwa i jinëch słowiańsczich krajów”] [[Kategòrëjô:Gmina Kòscérzna]] [[Kategòrëjô:Kaszëbë]] 29dabsj0pf92wr6chm4z6arqyjxz4vc 204484 204480 2026-07-05T19:52:59Z Orbitminis 18250 w historicznym pòrządkù, òdj., 1975-1998 woj. 204484 wikitext text/x-wiki {{WYŚWIETLANYTYTUŁ:Zeleniô}}{{Wies infobox|Wies=Zeleniô|rodzôcz_wsë=Zelenie|wòjewództwò=pòmòrsczé|kréz=kòscérsczi|gmina=Kòscérzna|szôłtëstwò=Zeleniô|lëdztwò=196|rok=2021|pòcztowi_kòd=83-422|gradëN=54|minutëN=07|sekùndëN=22|gradëE=18|sekùndëE=17|minutëE=05|galerëjô_commons=Category:Zielenin|òdjimk=Ciconia_ciconia_nest_Zielenin.jpg|reg_tôfla=GKS|SIMC=0164931}} '''Zeleniô''' ([[Pòlsczi jãzëk|pòl]]. ''Zielenin'', [[Niemiecczi jãzëk|niem]]. ''Zelenin'', òb lata 1942-1945 ''Altehütte'')<ref>wëbiér zdrzódłów, M. Kubicki ''Germanizacja nazw miejscowości w okręgu Rzeszy Gdańsk-Prusy Zachodnie 1939-1945'' (pòl.), Instytut Pamięci Narodowej, Gduńsk-Warszawa, 2022</ref> – [[Kaszëbë|kaszëbskô]] wies leżącô na [[Kaszëbsczé Pòjezerzé|Kaszëbsczim Pòjezerzim]], nad rzéką [[Wierzëca|Wierzëcą]] przë [[Wòjewódzkô darga|wòjewódzczi dardze]] nr [[Òbrôzk:Tabliczka DW221.svg|bezramki|18x18px]] w [[Pòmòrsczé wòjewództwò|pòmòrsczim wòjewództwie]], w [[Kòscérsczi kréz|kòscérsczim krézu]], w [[Gmina Kòscérzna|gminie Kòscérzna]]. Wies je sedzbą szôłtëstwa Zeleniô. W 1890 rokù pòwsta tuwò dwalatnô Gbùrskô Szkòła. W latach 1975–1998 môlëzna administracyjno pòłożonô bëła w [[Gduńsczé wòjewództwò|gduńsczim wòjewództwie]]. W 2021 rokù w Zelenie mieszkało 196 òsób<ref>Polska w Liczbach, ''[https://www.polskawliczbach.pl/wies_Zielenin Wieś Zielenin w liczbach]'' (pòl.), przëstãp z 5.07.2026 r.</ref>. == Bùtnowé lënczi == {{Commons}} * [http://dir.icm.edu.pl/pl/Slownik_geograficzny/Tom_XIV/593 ''Zielenina'' w „Geògraficznym słowôrzu Pòlsczégò Królestwa i jinëch słowiańsczich krajów”] == Przëpisë == [[Kategòrëjô:Gmina Kòscérzna]] [[Kategòrëjô:Kaszëbë]] cyb4ve4ikt2t5h69xn1hnjt507yb54r 204488 204484 2026-07-05T20:27:20Z Terrus38 14026 204488 wikitext text/x-wiki {{WYŚWIETLANYTYTUŁ:Zeleniô}}{{Wies infobox|Wies=Zeleniô|rodzôcz_wsë=Zelenie|wòjewództwò=pòmòrsczé|kréz=kòscérsczi|gmina=Kòscérzna|szôłtëstwò=Zeleniô|lëdztwò=196|rok=2021|pòcztowi_kòd=83-422|gradëN=54|minutëN=07|sekùndëN=22|gradëE=18|sekùndëE=17|minutëE=05|galerëjô_commons=Category:Zielenin|òdjimk=Ciconia_ciconia_nest_Zielenin.jpg|reg_tôfla=GKS|SIMC=0164931}} '''Zeleniô''' ([[Pòlsczi jãzëk|pòl]]. ''Zielenin'', [[Niemiecczi jãzëk|niem]]. ''Zelenin'', òb lata 1942-1945 ''Altehütte'')<ref>wëbiér zdrzódłów, M. Kubicki ''Germanizacja nazw miejscowości w okręgu Rzeszy Gdańsk-Prusy Zachodnie 1939-1945'' (pòl.), Instytut Pamięci Narodowej, Gduńsk-Warszawa, 2022</ref> – [[Kaszëbë|kaszëbskô]] wies leżącô na [[Kaszëbsczé Pòjezerzé|Kaszëbsczim Pòjezerzim]], nad rzéką [[Wierzëca|Wierzëcą]] przë [[Wòjewódzkô droga|wòjewódzczi drodze]] nr [[Òbrôzk:Tabliczka DW221.svg|bezramki|18x18px]] w [[Pòmòrsczé wòjewództwò|pòmòrsczim wòjewództwie]], w [[Kòscérsczi kréz|kòscérsczim krézu]], w [[Gmina Kòscérzna|gminie Kòscérzna]]. Wies je sedzbą szôłtëstwa Zeleniô. W 1890 rokù pòwsta tuwò dwalatnô Gbùrskô Szkòła. W latach 1975–1998 môlëzna administracyjno pòłożonô bëła w [[Gduńsczé wòjewództwò|gduńsczim wòjewództwie]]. W 2021 rokù w Zelenie mieszkało 196 òsób<ref>Polska w Liczbach, ''[https://www.polskawliczbach.pl/wies_Zielenin Wieś Zielenin w liczbach]'' (pòl.), przëstãp z 5.07.2026 r.</ref>. == Bùtnowé lënczi == {{Commons}} * [http://dir.icm.edu.pl/pl/Slownik_geograficzny/Tom_XIV/593 ''Zielenina'' w „Geògraficznym słowôrzu Pòlsczégò Królestwa i jinëch słowiańsczich krajów”] == Przëpisë == [[Kategòrëjô:Gmina Kòscérzna]] [[Kategòrëjô:Kaszëbë]] basg0hyfbn3l38ruerwdv0ncw5j4ijv 204501 204488 2026-07-06T11:21:26Z Iketsi 3254 [[Pòlsczi jãzëk|pòl]].→{{jãz.|pl}}; [[Niemiecczi jãzëk|niem]].→{{jãz.|de}} 204501 wikitext text/x-wiki {{Wies infobox|Wies=Zeleniô|rodzôcz_wsë=Zelenie|wòjewództwò=pòmòrsczé|kréz=kòscérsczi|gmina=Kòscérzna|szôłtëstwò=Zeleniô|lëdztwò=196|rok=2021|pòcztowi_kòd=83-422|gradëN=54|minutëN=07|sekùndëN=22|gradëE=18|sekùndëE=17|minutëE=05|galerëjô_commons=Category:Zielenin|òdjimk=Ciconia_ciconia_nest_Zielenin.jpg|reg_tôfla=GKS|SIMC=0164931}} '''Zeleniô''' ({{jãz.|pl}} ''Zielenin'', {{jãz.|de}} ''Zelenin'', òb lata 1942–1945 ''Altehütte'')<ref>wëbiér zdrzódłów, M. Kubicki ''Germanizacja nazw miejscowości w okręgu Rzeszy Gdańsk-Prusy Zachodnie 1939–1945'' ({{jãz.|pl}}), Instytut Pamięci Narodowej, Gduńsk-Warszawa, 2022</ref> – [[Kaszëbë|kaszëbskô]] wies leżącô na [[Kaszëbsczé Pòjezerzé|Kaszëbsczim Pòjezerzim]], nad rzéką [[Wierzëca|Wierzëcą]] przë [[Wòjewódzkô droga|wòjewódzczi drodze]] nr [[Òbrôzk:Tabliczka DW221.svg|bezramki|18x18px]] w [[Pòmòrsczé wòjewództwò|pòmòrsczim wòjewództwie]], w [[Kòscérsczi kréz|kòscérsczim krézu]], w [[Gmina Kòscérzna|gminie Kòscérzna]]. Wies je sedzbą szôłtëstwa Zeleniô. W 1890 rokù pòwsta tuwò dwalatnô Gbùrskô Szkòła. W latach 1975–1998 môlëzna administracyjno pòłożonô bëła w [[Gduńsczé wòjewództwò|gduńsczim wòjewództwie]]. W 2021 rokù w Zelenie mieszkało 196 òsób<ref>Polska w Liczbach, ''[https://www.polskawliczbach.pl/wies_Zielenin Wieś Zielenin w liczbach]'' ({{jãz.|pl}}), przëstãp z 5.07.2026 r.</ref>. == Bùtnowé lënczi == {{Commons}} * [http://dir.icm.edu.pl/pl/Slownik_geograficzny/Tom_XIV/593 ''Zielenina'' w „Geògraficznym słowôrzu Pòlsczégò Królestwa i jinëch słowiańsczich krajów”] == Przëpisë == {{Przëpisë}} {{Kòntrola}} [[Kategòrëjô:Gmina Kòscérzna]] [[Kategòrëjô:Kaszëbë]] sikjzg9en89x77sq4q37zymhy459ylt 204505 204501 2026-07-06T11:25:56Z Iketsi 3254 Bùtnowé lënczi 204505 wikitext text/x-wiki {{Wies infobox|Wies=Zeleniô|rodzôcz_wsë=Zelenie|wòjewództwò=pòmòrsczé|kréz=kòscérsczi|gmina=Kòscérzna|szôłtëstwò=Zeleniô|lëdztwò=196|rok=2021|pòcztowi_kòd=83-422|gradëN=54|minutëN=07|sekùndëN=22|gradëE=18|sekùndëE=17|minutëE=05|galerëjô_commons=Category:Zielenin|òdjimk=Ciconia_ciconia_nest_Zielenin.jpg|reg_tôfla=GKS|SIMC=0164931}} '''Zeleniô''' ({{jãz.|pl}} ''Zielenin'', {{jãz.|de}} ''Zelenin'', òb lata 1942–1945 ''Altehütte'')<ref>wëbiér zdrzódłów, M. Kubicki ''Germanizacja nazw miejscowości w okręgu Rzeszy Gdańsk-Prusy Zachodnie 1939–1945'' ({{jãz.|pl}}), Instytut Pamięci Narodowej, Gduńsk-Warszawa, 2022</ref> – [[Kaszëbë|kaszëbskô]] wies leżącô na [[Kaszëbsczé Pòjezerzé|Kaszëbsczim Pòjezerzim]], nad rzéką [[Wierzëca|Wierzëcą]] przë [[Wòjewódzkô droga|wòjewódzczi drodze]] nr [[Òbrôzk:Tabliczka DW221.svg|bezramki|18x18px]] w [[Pòmòrsczé wòjewództwò|pòmòrsczim wòjewództwie]], w [[Kòscérsczi kréz|kòscérsczim krézu]], w [[Gmina Kòscérzna|gminie Kòscérzna]]. Wies je sedzbą szôłtëstwa Zeleniô. W 1890 rokù pòwsta tuwò dwalatnô Gbùrskô Szkòła. W latach 1975–1998 môlëzna administracyjno pòłożonô bëła w [[Gduńsczé wòjewództwò|gduńsczim wòjewództwie]]. W 2021 rokù w Zelenie mieszkało 196 òsób<ref>Polska w Liczbach, ''[https://www.polskawliczbach.pl/wies_Zielenin Wieś Zielenin w liczbach]'' ({{jãz.|pl}}), przëstãp z 5.07.2026 r.</ref>. == Przëpisë == {{Przëpisë}} == Bùtnowé lënczi == {{Commons}} * [http://dir.icm.edu.pl/pl/Slownik_geograficzny/Tom_XIV/593 ''Zielenina'' w „Geògraficznym słowôrzu Pòlsczégò Królestwa i jinëch słowiańsczich krajów”] {{Kòntrola}} [[Kategòrëjô:Gmina Kòscérzna]] [[Kategòrëjô:Kaszëbë]] bo8jz7wa6391awb70nr13h3chknbbfs Lësatô kania 0 9406 204478 203353 2026-07-05T19:07:37Z Rabùszniczka 19710 Napisa jem prawie całosc òd nowa z mëslą żebë stil béł barżi encyklopediczny, zmieniła jem ùkłôd abë lepi sã to czëtało. Całosc to tłomacznié z engwiki i plwiki. Wcyg nót je napisac dzél ò statuse òchroné i zrobic kòrekta. Brëkùje nama infobòksu na gatënczi :( 204478 wikitext text/x-wiki '''Lësatô kania''' (''Milvus milvus'') – gatënk drapieżnégò ptôcha z rodzëznë [[Accipitridae|Jastrzibòwatëch]]. Gatënk ten dzysdnia gniôzdëje w Eùropie, chòc w dôwnoce rozmnażôł sã téż w zôpadny Azji i północno-zôpadny Africe<ref>BirdLife International (2020). "Milvus milvus". IUCN Red List of Threatened Species. 2020 e.T22695072A181651010. doi:10.2305/IUCN.UK.2020-3.RLTS.T22695072A181651010.en. Retrieved 19 November 2021.</ref>. == Systematika == [[Òbrôzk:Lunak_cerveny.jpg|mały|Dozdrzeniałô kania.]] Lësatô kania òstała òpisónô przez Szwedzczégò rôdnika [[Karol Linneùsz|Karla Linneùsza]] w 1758 w dzesąti edycje jegò dokôzu ''Systema Naturae'' pòd binominalną gatënkòwą pòzwą ''Falco milvus''<ref>[[Karol Linneùsz|Linneùsz, Karol]] (1758) [https://www.biodiversitylibrary.org/page/726996#page/106/mode/1up Systema naturae per regna tria naturae, secundum classes, ordines, genera, species, cum characteribus, differentiis, synonymis, locis. Tomus I. Editio decima, reformata] (w łacëznie), Tom pierszi, Vol. 1. Stockholm: Laurentius Salvius. p. 89. "F. cera flava, cauda forsicata, corpore ferrugineo, capite albidiore."</ref>. Słowò ''Milvus'' je łaceńską pòzwą na tegò ptôcha. W 1799 francëzczi rodnik Bernard Germain de Lacépède przeniósł gatënk do rodz''aju Milvus.'' Wëapartniwóné są dwa pòdgatënczi Milvus milvus * ''M. milvus milvus'' <small>(Linnaeus, 1758)</small> – '''lësatô kania.''' * ''M. milvus fasciicauda'' <small>E. Hartert, 1914</small> – '''zelonogłownô kania''' – endemit Òstrowów Zelony Głowë, prawie na gwës wymarłi. W rodzaju ''Milvus'' są téż dwa jinszé gatënczi: czôrnô kania i egipskô kania. Znóné są przëtrôfczi ùdany hybridizacji midzë lësatima i czôrnyma kaniami. === Hybridizacjô === [[Òbrôzk:Obernberg am Inn Adlerwarte Rotmilan-0136.jpg|mały|250x250px|Lësatô kania, ù skòlników z Adlerwarte Obernberg am Inn, [[Aùstriô]].]] Rodzôj ''Milvus'' zawierô w so jesz dwa jinszé gatënczi - Czôrną kaniã i Egipską kaniã<ref>Gill, Frank; Donsker, David, eds. (2018). [http://www.worldbirdnames.org/bow/raptors/ "New World vultures, Secretarybird, kites, hawks, eagles]". World Bird List Version 8.2. International Ornithologists' Union. Retrieved 24 September 2018.</ref>. Są znóné przëtrôfczi w jaczich lësatim kanióm ùdało sã rozmnożëc z czôrnyma kaniamim, dzało sã to w niewòli jak i dzëczim stónie jak np. na Òstrowach Zelony Głowë. Strefa hybridizacje w jaczi òba gatënczi òkazyjnie sã krziżują jistnieje w Centralny Eùropie.<ref>Heneberg, Petr; Dolinay, Matej; Matuší, Hynek; Pfeiffer, Thomas; Nachtigall, Winfried; Bizos, Jiri; Šimčiková, Daniela; Literák, Ivan (2016). [https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0159202 "Conservation of the Red Kite Milvus milvus (Aves: Accipitriformes) Is Not Affected by the Establishment of a Broad Hybrid Zone with the Black Kite Milvus migrans migrans in Central Europe"]. PLoS ONE. 11 (7). Retrieved 30 August 2025.</ref> Geneticzné badérowaniô kaniów z ti rëmi nie wëkazałe niżódnégò [[mtDNA]] czôrnëch kaniów w lësatich kaniach i vice versa. Przez to, że ten ôrt DNA je przekazëwóny przez nënczi, aùtorzë badaniô sùgerują, że mieszóné samice są bezpłodné w zgòdze z [[Prawò Haldana|prawã Haldana]]. W 2006 rokù w Szkòcji doszło do ùdanégò rozrodu samca czôrny kanie z samicą lësowati kanie w jaczim ùdało jim sã wëchòwac dwa pòdlatawce. == Wëstãpòwanié == [[Òbrôzk:Milvus milvus distr.png|lewo|mały|Apfelzynowi - òkres lągòwi, Mòdri - zëmòwanié, Zelony - całoroczny]] Lësaté kanie są ptôchama jaczé zamieszkùją lëstowaté lasë, łączi, pòla dolenë i sampòwaté pògreńcza. Pòchòdzą z zôpadny [[Palearktycznô krajina|Palearktyczny krajinë]], tak tej wszëtczé znóné rodné parë (jaczich je kòl 32,200 - 37,000) młodzą sã w Eùropie. Przede wszëtczim żëje w zôpadny, strzôdkòwi i pôłniowi Eùropie, nielëcznie téż w wschòdnym parce Azji Mniszi i w północny Africe, jaczi status nie je gwësny - lãgòwô pòpùlacja z Maroka je ùznôwónô za wëmarłą, chòc sómé kanie dali zëmùje w tim kraju. Dawni gatënk mòżna bëło pòtkac téż na Òstrowach Kanarijsczich i Òstrowach Zelonégò Przëlądka(?) gdze prawie na gwës wëmôrł<ref>Garcia-del-Rey, E. (2011). Field Guide to the Birds of Macaronesia. Azores, Madeira, Canary Islands, Cape Verde. Lynx Edicions, Barcelona, Spain.</ref>. Kania je wãdrownô, chòc to zanôlégô òd ji pòpùlacji - ptôchë z pôłni mieszkają ù se całi rok, a te żëjącé w ùmiarkòwónym klimace migrëją òd zélnikã do séwnikã nad Strzôdzemné Mòrze, abë wrócëc do swòjich gniôzd kòl strëmiannikã. W przërównanim do [[Czôrnô kania|czôrny kanie]] ji zasyg je ògraniczony. W Pòlsce je to nielëczny lągòwi ptôch. Kanie są spòtikóné blós na zôpadze i nordowim wschòdze kraju ( Wiôlgôpòlska, [[Pòmòrskô|Pòmòrze]], nordowi part [[Dolny Sląsk|Dolnégò Sląska]] i téż [[Warmiô]] i [[Mazurë]])<ref>Ludwik Tomiałojć, Tadeusz Stawarczyk: Awifauna Polski. Rozmieszczenie, liczebność i zmiany. Wrocław: PTPP "pro Natura", 2003, s. 203. [[:pl:Specjalna:Książki/839196261X|ISBN 83-919626-1-X.]]</ref> W zaòstałich regionach je rzôdkô i je widëwónô dërch w czas migracji. Niechtërné kanie w Centralny Eùropie mògą zëmòwac kòl rakòwiszczów lub jinnëch môlów òbfitëch w pòkarm, le zachòwanié to je nieregùlarné i robią to leno pòjedinczé òsobniczi<ref>Marcin Karetta: Atlas ptaków. Pascal, 2010. I[[:pl:Specjalna:Książki/9788375136555|SBN 978-83-7513-655-5.]]</ref>. == Znanczi gatënkù == [[Òbrôzk:Schwanz-Milane.jpg|mały|Ògónë kaniów pò prawi - czôrnô kania, na lewò lësatô.]] [[Òbrôzk:Red Kite (Milvus milvus) skull at the Royal Veterinary College anatomy museum.JPG|mały|Czaszka ''Milvus Milvus''.]] Ptóch ò wëlecałi sylwetkã i bestrim òpiórzenim. Jegò cało mô 60 - 72 cm długòscy<ref>Orta, J., Christie, D.A. & Garcia, E.F.J.: [https://www.hbw.com/species/red-kite-milvus-milvus Red Kite (Milvus milvus)]. [w:] del Hoyo, J., Elliott, A., Sargatal, J., Christie, D.A. & de Juana, E. (eds.). Handbook of the Birds of the World Alive.</ref>, i 175 - 195 cm w rozpiertoscy lecëdłów. Samce ważą òd 800 do 1200 gramów, timczasã samice są wiãkszé ważą midzë 1000, a 1300 gramów. Pòza różnicą wôdze midzë płcama ptôcha ni ma wiãkszich różniców - òbie płce są szlachòwno ùbarwióné - przeważnie dërdzawé cało z cemniejszima plecama i białima akcentama na lëcadłach. Ògón czerwònobruny z wiérzchù i jasnoszari òd spòdu, dosc mòcno wcãti (niżóden eùropejsczi drapieżnik ni mô tak mòcnégò wcãcô jak kania). Temù téż ògón je nôbarżi rozpòznowóną znanką tegò ptôcha w loce na wiôldżich wësokòscach. Głowa, szëja i placë òbsadzeniô skrzidłów jasnopòpielaté ze sztrichòwanim. Wiérzch cała dërdzawòbruny, spòdk cała jasniészi, sztrichòwóny. Na dôlny starnie skrzidła wildżi biôłi plachc, na wiérzchny starnie przeznórtny pôsk. Żółti dzëb z czórnym kùńcã, żółté nodżi. Rzadkò zdarzają sã ptôchë z [[Leùcyzm|leùcysticzną]] fòrmą. Szlachùjącô za nią czôrnô kania je përznã mniészô, mô wiele cemniészé òpiórzenié i mni wcãti ògón. W przërównanim do [[Mësznik|mësznika]] je wiele wikszô i mô dłëższé skrzidła. Pò lãgòwim czasu kanie mògą noclegòwac w karnach dochódôjącëch nawetka do sto sztëk, chòc nie je tak drësznô jak czôrnô kania. Lësowatô kania czãsto lecy na prostëch, abò letkò zdżãtëch skrzidłach, co w sztôłce przëbôcziwô lëtrã M. W chùtczim i chwatczim loce mô prawie wiedno rozkòscérzony ògón, chtërny czasã sã przekrzéwiô. Kanie żëją kòl 25 lat, chòc w 2023 w [[Wiôlgô Britaniô|Wiôldżi Britanii]] bëła nalezónô kania chtërnô dożëła 29 lat.<ref>[https://www.bbc.co.uk/news/uk-england-oxfordshire-65646200 "One of the first red kites released into Thames Valley dies"]. BBC News. 20 May 2023. Retrieved 31 July 2023.</ref> == Biotop i jôda == [[Òbrôzk:Red Kite R1 4513.jpg|mały|Lësatô kania w loce, [[Wiôlgô Britaniô]]]] === Biotop === Widné lëstowaté i mieszóné stôré lasë rosnącé kòl łąków i pól, czãstò w òkòli dolën rzek, stôwów, czë skalëstich ùbrzégòw. Spòtikónô je na terenach niżënnech, jak téż w górach. === Jôda === Kanie są generalisticznymi drapieżnikama i scërzożércama, przez co jich dieta je tak różnorodnô, jak jich strzôdwiszcze. Pòżiwiają sã môłimi [[Krãgòwce|krãgòwcama]], przede wszëtczim drobnyma [[Sysczi|syskama]], taczimi jak: [[Toczk|toczczi]], czë [[Grëzôk|grëzôczi]], ale téz [[Wieszczórka|wieszczórczi]], [[Òwôd|òwadë]] i [[Wãdzëbôk|wãdzëbôczi]]. Zdarzëwô jim sã téż jesc rebë wëszmërgniãté na sztrąd. Ptôche te baro spôrwnie zbierają w czase nisczéò lotu dzãka czemù jachtują na swòje òfiare, rozmieją nawetka wëcygnąc bierno płëwającé na wòdze rebë. [[Òbrôzk:Rote Milane mit Beute.jpg|lewo|mały|Pôra kaniów z òfiarą, [[Niemieckô]].]] Wòlą chwacëc żëwą zdobëcz, niż jesc scyrzã. Zdôrzô jim sã atakòwac w célu kradzéżë òfiarë jinszé rabùszné ptôchë (nawetka te wiãkszé òd nich), czim badérowie dolmaczą znôjdowóné w ji wnãtrznoscach resztczi zwierzów, jaczich kanie nie mògłe bë ùpòlowac. Swòji òfiarë wëzerają z gałãzy drzew, chòc òwadë i môłé sysczi rozmie chwacëc nawetka w czase wòlnégi chòdzeniô. Pòza tim lësati kanie czasã plundrëją gniôzda [[Krëkòwaté|krëkòwatëch]]. == Lągòwi cząd == Kanie rozmnażają sã dërch czej staną sã dwa lata stôri, chòc zdarzają sã przëtrôfczi roczniaków jaczim ùdało sã młodżec<ref>Evans, I. M.; Cordero, P. J.; Parkin, D. T. (1998). "Successful breeding at one year of age by Red Kites Milvus milvus in southern England". Ibis. 140 (1): 53–57</ref>. Są to mònogamiczné ptôchë, chtërné w pòpùlacjach stałëch pòzostają w ti sami pôrze przez zëmã, òsoblewò jeżle ptôchë pòzostają na tim sómym terytorium. Wãdrowné kanie pò zëmie wracają do tegò samégò gniôzda, czim pòdtrzimùją "wiernosc" midzë ptôchama. === Gniôzdo === [[Òbrôzk:Milmil njg 980613.jpg|lewo|mały|Młodé kanie w gniôzdze. [[Berlëno]], [[Niemieckô]].]] Nôczãscy bùdowóné je na lëstowatim drzewie, na rozgałązeniu wietwiów, abò kònarów na wësokòscy midzë 12 a 15 métrama. Kanie wic wëbierają drzewa na skraju lasu. Bùdową gniôzda zajmùją sã òbie płce. Samc halô wietwie ò długòscy 30-50 cm, a samica je ukłôdô. Pòdstawą je grëbô galąz, scëlónô mitczimi rosclënama, ale téż różnyma smiecama np. papierã, szmatama, kòscama, czë skórama. Òba ptôche dodają wiãcy materiałów òbczas inkùbacji i òdchòwaniô młodëch. Kanie jedno gniôzdô ùżiwają przez wiele lat<ref name=":0">Cramp, Stanley; et al., eds. (1980). "Milvus milvus Red Kite". Handbook of the Birds of Europe the Middle East and North Africa: The Birds of the Western Palearctic. Vol. II: Hawks to Bustards. Oxford: Oxford University Press. pp. 36–44. <nowiki>ISBN 978-0-19-857505-4</nowiki>.</ref>. Òkazjonalnie zdarzô sã kanióm wëkòrzistac stôré gniôzda [[Mësznik|mëszników]]. Òbczas lągù kanie są agresywné wòbec rabùsznëch ptôchów, tak tej w òkòli jich gniôzda nie młodzą sã jinszé [[szpòniasté]]. === Jaje === [[Òbrôzk:Milvus milvus MHNT.ZOO.2010.11.80.15.jpg|mały|Jaje lësati kani.]] Kanie niosą sã leno rôz w rokù midzë łżëkwiatã a majã<ref name=":0" />. Pòdczas jednégò gòdowégò cządu kania niese wic midzë jednym, a trzema jajami, chòc òdnotowóné bëłë przëtrôfczi gdze w lągù bëło jaż piãc jôj. Jaje są białé z zemisto-czerwionyma kropkama. Statistycznie jajo ważi 63 gramë i mô wëmiarë 57 mm x 47 mm.<ref name=":0" /> Jeżle ląg òstónié stracóny, samica zôs zniese jaja.<ref name=":0" /> === Wësadowanié i dorôstanié === Jaja wësadowóné są przez kòl 30 dni przez samicã. Samc w tim czasu przenosy ji pòkarm, chòc czasã zastãpùje swòją partnerkã czej òna lecy jachtowac. Pizglãta pierszé dwa tydzenié żëcô spãdzają w gniôzdzë razã z nënką, chtërnô karmi je jôdã halóną przez samca. Pózni òba ptôchë latają pò jestkù dlô młodëch. Pò 14 dniach młodé sóme mùszą karmic z przëniesonégò do gniôzdã jedzeniô. Kòl 45 dnia żëcô pizglãta zaczinają wspinac sã na gałãze kòło gniôzda, le rzadkò pòdlatują przed 48-50 dnia żëcô, a czasã nawetka do wiekù 60 - 70 dniów. Pòdlatwce spãdzają jesz dwa - trzë tydzenie òkòli gniôzda z dokarmiającymi jich rodzëcama. Pò dwùch latach młodé stają sã dozdrzëniałimi ptôchama. == Przëpisë == {{Przëpisë}} == Bùtnowé lënczi == {{Commons}} [[Kategòrëjô:Ptôchë]] [[Kategòrëjô:Jastrzibowaté]] [[Kategòrëjô:Ptôchë Eùropë]] [[Kategòrëjô:Rabùszné Ptôchë]] hlxywewfd27ddt58soxvxy8zt6w392l 204483 204478 2026-07-05T19:50:40Z Rabùszniczka 19710 204483 wikitext text/x-wiki '''Lësatô kania''' (''Milvus milvus'') – gatënk drapieżnégò ptôcha z rodzëznë [[Accipitridae|Jastrzibòwatëch]]. Gatënk ten dzysdnia gniôzdëje w Eùropie, chòc w dôwnoce rozmnażôł sã téż w zôpadny Azji i północno-zôpadny Africe<ref>BirdLife International (2020). "Milvus milvus". IUCN Red List of Threatened Species. 2020 e.T22695072A181651010. doi:10.2305/IUCN.UK.2020-3.RLTS.T22695072A181651010.en. Retrieved 19 November 2021.</ref>. == Systematika == [[Òbrôzk:Lunak_cerveny.jpg|mały|Dozdrzeniałô kania.]] Lësatô kania òstała òpisónô przez Szwedzczégò rôdnika [[Karol Linneùsz|Karla Linneùsza]] w 1758 w dzesąti edycje jegò dokôzu ''Systema Naturae'' pòd binominalną gatënkòwą pòzwą ''Falco milvus''<ref>[[Karol Linneùsz|Linneùsz, Karol]] (1758) [https://www.biodiversitylibrary.org/page/726996#page/106/mode/1up Systema naturae per regna tria naturae, secundum classes, ordines, genera, species, cum characteribus, differentiis, synonymis, locis. Tomus I. Editio decima, reformata] (w łacëznie), Tom pierszi, Vol. 1. Stockholm: Laurentius Salvius. p. 89. "F. cera flava, cauda forsicata, corpore ferrugineo, capite albidiore."</ref>. Słowò ''Milvus'' je łaceńską pòzwą na tegò ptôcha. W 1799 francëzczi rodnik Bernard Germain de Lacépède przeniósł gatënk do rodz''aju Milvus.'' Wëapartniwóné są dwa pòdgatënczi Milvus milvus * ''M. milvus milvus'' <small>(Linnaeus, 1758)</small> – '''lësatô kania.''' * ''M. milvus fasciicauda'' <small>E. Hartert, 1914</small> – '''zelonogłownô kania''' – endemit Òstrowów Zelony Głowë, prawie na gwës wymarłi. W rodzaju ''Milvus'' są téż dwa jinszé gatënczi: czôrnô kania i egipskô kania. Znóné są przëtrôfczi ùdany hybridizacji midzë lësatima i czôrnyma kaniami. === Hybridizacjô === [[Òbrôzk:Obernberg am Inn Adlerwarte Rotmilan-0136.jpg|mały|250x250px|Lësatô kania, ù skòlników z Adlerwarte Obernberg am Inn, [[Aùstriô]].]] Rodzôj ''Milvus'' zawierô w so jesz dwa jinszé gatënczi - Czôrną kaniã i Egipską kaniã<ref>Gill, Frank; Donsker, David, eds. (2018). [http://www.worldbirdnames.org/bow/raptors/ "New World vultures, Secretarybird, kites, hawks, eagles]". World Bird List Version 8.2. International Ornithologists' Union. Retrieved 24 September 2018.</ref>. Są znóné przëtrôfczi w jaczich lësatim kanióm ùdało sã rozmnożëc z czôrnyma kaniamim, dzało sã to w niewòli jak i dzëczim stónie jak np. na Òstrowach Zelony Głowë. Strefa hybridizacje w jaczi òba gatënczi òkazyjnie sã krziżują jistnieje w Centralny Eùropie.<ref>Heneberg, Petr; Dolinay, Matej; Matuší, Hynek; Pfeiffer, Thomas; Nachtigall, Winfried; Bizos, Jiri; Šimčiková, Daniela; Literák, Ivan (2016). [https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0159202 "Conservation of the Red Kite Milvus milvus (Aves: Accipitriformes) Is Not Affected by the Establishment of a Broad Hybrid Zone with the Black Kite Milvus migrans migrans in Central Europe"]. PLoS ONE. 11 (7). Retrieved 30 August 2025.</ref> Geneticzné badérowaniô kaniów z ti rëmi nie wëkazałe niżódnégò [[mtDNA]] czôrnëch kaniów w lësatich kaniach i vice versa. Przez to, że ten ôrt DNA je przekazëwóny przez nënczi, aùtorzë badaniô sùgerują, że mieszóné samice są bezpłodné w zgòdze z [[Prawò Haldana|prawã Haldana]]. W 2006 rokù w Szkòcji doszło do ùdanégò rozrodu samca czôrny kanie z samicą lësowati kanie w jaczim ùdało jim sã wëchòwac dwa pòdlatawce. == Wëstãpòwanié == [[Òbrôzk:Milvus milvus distr.png|lewo|mały|Apfelzynowi - òkres lągòwi, Mòdri - zëmòwanié, Zelony - całoroczny]] Lësaté kanie są ptôchama jaczé zamieszkùją lëstowaté lasë, łączi, pòla dolenë i sampòwaté pògreńcza. Pòchòdzą z zôpadny [[Palearktycznô krajina|Palearktyczny krajinë]], tak tej wszëtczé znóné rodné parë (jaczich je kòl 32,200 - 37,000) młodzą sã w Eùropie. Przede wszëtczim żëje w zôpadny, strzôdkòwi i pôłniowi Eùropie, nielëcznie téż w wschòdnym parce Azji Mniszi i w północny Africe, jaczi status nie je gwësny - lãgòwô pòpùlacja z Maroka je ùznôwónô za wëmarłą, chòc sómé kanie dali zëmùje w tim kraju. Dawni gatënk mòżna bëło pòtkac téż na Òstrowach Kanarijsczich i Òstrowach Zelonégò Przëlądka(?) gdze prawie na gwës wëmôrł<ref>Garcia-del-Rey, E. (2011). Field Guide to the Birds of Macaronesia. Azores, Madeira, Canary Islands, Cape Verde. Lynx Edicions, Barcelona, Spain.</ref>. Kania je wãdrownô, chòc to zanôlégô òd ji pòpùlacji - ptôchë z pôłni mieszkają ù se całi rok, a te żëjącé w ùmiarkòwónym klimace migrëją òd zélnikã do séwnikã nad Strzôdzemné Mòrze, abë wrócëc do swòjich gniôzd kòl strëmiannikã. W przërównanim do [[Czôrnô kania|czôrny kanie]] ji zasyg je ògraniczony. W Pòlsce je to nielëczny lągòwi ptôch. Kanie są spòtikóné blós na zôpadze i nordowim wschòdze kraju ( Wiôlgôpòlska, [[Pòmòrskô|Pòmòrze]], nordowi part [[Dolny Sląsk|Dolnégò Sląska]] i téż [[Warmiô]] i [[Mazurë]])<ref>Ludwik Tomiałojć, Tadeusz Stawarczyk: Awifauna Polski. Rozmieszczenie, liczebność i zmiany. Wrocław: PTPP "pro Natura", 2003, s. 203. [[:pl:Specjalna:Książki/839196261X|ISBN 83-919626-1-X.]]</ref> W zaòstałich regionach je rzôdkô i je widëwónô dërch w czas migracji. Niechtërné kanie w Centralny Eùropie mògą zëmòwac kòl rakòwiszczów lub jinnëch môlów òbfitëch w pòkarm, le zachòwanié to je nieregùlarné i robią to leno pòjedinczé òsobniczi<ref>Marcin Karetta: Atlas ptaków. Pascal, 2010. I[[:pl:Specjalna:Książki/9788375136555|SBN 978-83-7513-655-5.]]</ref>. == Znanczi gatënkù == [[Òbrôzk:Schwanz-Milane.jpg|mały|Ògónë kaniów pò prawi - czôrnô kania, na lewò lësatô.]] [[Òbrôzk:Red Kite (Milvus milvus) skull at the Royal Veterinary College anatomy museum.JPG|mały|Czaszka ''Milvus Milvus''.]] Ptóch ò wëlecałi sylwetkã i bestrim òpiórzenim. Jegò cało mô 60 - 72 cm długòscy<ref>Orta, J., Christie, D.A. & Garcia, E.F.J.: [https://www.hbw.com/species/red-kite-milvus-milvus Red Kite (Milvus milvus)]. [w:] del Hoyo, J., Elliott, A., Sargatal, J., Christie, D.A. & de Juana, E. (eds.). Handbook of the Birds of the World Alive.</ref>, i 175 - 195 cm w rozpiertoscy lecëdłów. Samce ważą òd 800 do 1200 gramów, timczasã samice są wiãkszé ważą midzë 1000, a 1300 gramów. Pòza różnicą wôdze midzë płcama ptôcha ni ma wiãkszich różniców - òbie płce są szlachòwno ùbarwióné - przeważnie dërdzawé cało z cemniejszima plecama i białima akcentama na lëcadłach. Ògón czerwònobruny z wiérzchù i jasnoszari òd spòdu, dosc mòcno wcãti (niżóden eùropejsczi drapieżnik ni mô tak mòcnégò wcãcô jak kania). Temù téż ògón je nôbarżi rozpòznowóną znanką tegò ptôcha w loce na wiôldżich wësokòscach. Głowa, szëja i placë òbsadzeniô skrzidłów jasnopòpielaté ze sztrichòwanim. Wiérzch cała dërdzawòbruny, spòdk cała jasniészi, sztrichòwóny. Na dôlny starnie skrzidła wildżi biôłi plachc, na wiérzchny starnie przeznórtny pôsk. Żółti dzëb z czórnym kùńcã, żółté nodżi. Rzadkò zdarzają sã ptôchë z [[Leùcyzm|leùcysticzną]] fòrmą. Szlachùjącô za nią czôrnô kania je përznã mniészô, mô wiele cemniészé òpiórzenié i mni wcãti ògón. W przërównanim do [[Mësznik|mësznika]] je wiele wikszô i mô dłëższé skrzidła. Pò lãgòwim czasu kanie mògą noclegòwac w karnach dochódôjącëch nawetka do sto sztëk, chòc nie je tak drësznô jak czôrnô kania. Lësowatô kania czãsto lecy na prostëch, abò letkò zdżãtëch skrzidłach, co w sztôłce przëbôcziwô lëtrã M. W chùtczim i chwatczim loce mô prawie wiedno rozkòscérzony ògón, chtërny czasã sã przekrzéwiô. Kanie żëją kòl 25 lat, chòc w 2023 w [[Wiôlgô Britaniô|Wiôldżi Britanii]] bëła nalezónô kania chtërnô dożëła 29 lat.<ref>[https://www.bbc.co.uk/news/uk-england-oxfordshire-65646200 "One of the first red kites released into Thames Valley dies"]. BBC News. 20 May 2023. Retrieved 31 July 2023.</ref> == Biotop i jôda == [[Òbrôzk:Red Kite R1 4513.jpg|mały|Lësatô kania w loce, [[Wiôlgô Britaniô]]]] === Biotop === Widné lëstowaté i mieszóné stôré lasë rosnącé kòl łąków i pól, czãstò w òkòli dolën rzek, stôwów, czë skalëstich ùbrzégòw. Spòtikónô je na terenach niżënnech, jak téż w górach. === Jôda === Kanie są generalisticznymi drapieżnikama i scërzożércama, przez co jich dieta je tak różnorodnô, jak jich strzôdwiszcze. Pòżiwiają sã môłimi [[Krãgòwce|krãgòwcama]], przede wszëtczim drobnyma [[Sysczi|syskama]], taczimi jak: [[Toczk|toczczi]], czë [[Grëzôk|grëzôczi]], ale téz [[Wieszczórka|wieszczórczi]], [[Òwôd|òwadë]] i [[Wãdzëbôk|wãdzëbôczi]]. Zdarzëwô jim sã téż jesc rebë wëszmërgniãté na sztrąd. Ptôche te baro spôrwnie zbierają w czase nisczéò lotu dzãka czemù jachtują na swòje òfiare, rozmieją nawetka wëcygnąc bierno płëwającé na wòdze rebë. [[Òbrôzk:Rote Milane mit Beute.jpg|lewo|mały|Pôra kaniów z òfiarą, [[Niemieckô]].]] Wòlą chwacëc żëwą zdobëcz, niż jesc scyrzã. Zdôrzô jim sã atakòwac w célu kradzéżë òfiarë jinszé rabùszné ptôchë (nawetka te wiãkszé òd nich), czim badérowie dolmaczą znôjdowóné w ji wnãtrznoscach resztczi zwierzów, jaczich kanie nie mògłe bë ùpòlowac. Swòji òfiarë wëzerają z gałãzy drzew, chòc òwadë i môłé sysczi rozmie chwacëc nawetka w czase wòlnégi chòdzeniô. Pòza tim lësati kanie czasã plundrëją gniôzda [[Krëkòwaté|krëkòwatëch]]. == Lągòwi cząd == [[Òbrôzk:Horst des Rotmilans (Milvus milvus).JPG|mały|Gnôzdo kani.]] Kanie rozmnażają sã dërch czej staną sã dwa lata stôri, chòc zdarzają sã przëtrôfczi roczniaków jaczim ùdało sã młodżec<ref>Evans, I. M.; Cordero, P. J.; Parkin, D. T. (1998). "Successful breeding at one year of age by Red Kites Milvus milvus in southern England". Ibis. 140 (1): 53–57</ref>. Są to mònogamiczné ptôchë, chtërné w pòpùlacjach stałëch pòzostają w ti sami pôrze przez zëmã, òsoblewò jeżle ptôchë pòzostają na tim sómym terytorium. Wãdrowné kanie pò zëmie wracają do tegò samégò gniôzda, czim pòdtrzimùją "wiernosc" midzë ptôchama. === Gniôzdo === [[Òbrôzk:Milmil njg 980613.jpg|lewo|mały|Młodé kanie w gniôzdze. [[Berlëno]], [[Niemieckô]].]] Nôczãscy bùdowóné je na lëstowatim drzewie, na rozgałązeniu wietwiów, abò kònarów na wësokòscy midzë 12 a 15 métrama. Kanie wic wëbierają drzewa na skraju lasu. Bùdową gniôzda zajmùją sã òbie płce. Samc halô wietwie ò długòscy 30-50 cm, a samica je ukłôdô. Pòdstawą je grëbô galąz, scëlónô mitczimi rosclënama, ale téż różnyma smiecama np. papierã, szmatama, kòscama, czë skórama. Òba ptôche dodają wiãcy materiałów òbczas inkùbacji i òdchòwaniô młodëch. Kanie jedno gniôzdô ùżiwają przez wiele lat<ref name=":0">Cramp, Stanley; et al., eds. (1980). "Milvus milvus Red Kite". Handbook of the Birds of Europe the Middle East and North Africa: The Birds of the Western Palearctic. Vol. II: Hawks to Bustards. Oxford: Oxford University Press. pp. 36–44. <nowiki>ISBN 978-0-19-857505-4</nowiki>.</ref>. Òkazjonalnie zdarzô sã kanióm wëkòrzistac stôré gniôzda [[Mësznik|mëszników]]. Òbczas lągù kanie są agresywné wòbec rabùsznëch ptôchów, tak tej w òkòli jich gniôzda nie młodzą sã jinszé [[szpòniasté]]. === Jaje === [[Òbrôzk:Milvus milvus MHNT.ZOO.2010.11.80.15.jpg|mały|Jaje lësati kani.]] Kanie niosą sã leno rôz w rokù midzë łżëkwiatã a majã<ref name=":0" />. Pòdczas jednégò gòdowégò cządu kania niese wic midzë jednym, a trzema jajami, chòc òdnotowóné bëłë przëtrôfczi gdze w lągù bëło jaż piãc jôj. Jaje są białé z zemisto-czerwionyma kropkama. Statistycznie jajo ważi 63 gramë i mô wëmiarë 57 mm x 47 mm.<ref name=":0" /> Jeżle ląg òstónié stracóny, samica zôs zniese jaja.<ref name=":0" /> === Wësadowanié i dorôstanié === Jaja wësadowóné są przez kòl 30 dni przez samicã. Samc w tim czasu przenosy ji pòkarm, chòc czasã zastãpùje swòją partnerkã czej òna lecy jachtowac. Pizglãta pierszé dwa tydzenié żëcô spãdzają w gniôzdzë razã z nënką, chtërnô karmi je jôdã halóną przez samca. Pózni òba ptôchë latają pò jestkù dlô młodëch. Pò 14 dniach młodé sóme mùszą karmic z przëniesonégò do gniôzdã jedzeniô. Kòl 45 dnia żëcô pizglãta zaczinają wspinac sã na gałãze kòło gniôzda, le rzadkò pòdlatują przed 48-50 dnia żëcô, a czasã nawetka do wiekù 60 - 70 dniów. Pòdlatwce spãdzają jesz dwa - trzë tydzenie òkòli gniôzda z dokarmiającymi jich rodzëcama. Pò dwùch latach młodé stają sã dozdrzëniałimi ptôchama. == Galeriô == [[Òbrôzk:Leucistic Red Kite - Flickr - Andy Morffew.jpg|lewo|mały|Leùcysticzny òsobnik.]] [[Òbrôzk:Milhafre-real, Red Kite (52539996534).jpg|centruj|mały|Lësatô kania w Pòrtugalii.]] == Przëpisë == {{Przëpisë}} == Bùtnowé lënczi == {{Commons}} [[Kategòrëjô:Ptôchë]] [[Kategòrëjô:Jastrzibowaté]] [[Kategòrëjô:Ptôchë Eùropë]] [[Kategòrëjô:Rabùszné Ptôchë]] 45il73vxtxqbusulx7rg4txt17jgd6o 204486 204483 2026-07-05T20:24:11Z Terrus38 14026 kòrekta 204486 wikitext text/x-wiki '''Lësatô kania''' (''Milvus milvus'') – gatënk drapieżnégò ptôcha z rodzëznë [[Accipitridae|jastrzëbòwatëch]]. Gatënk ten dzysdnia gniôzdëje w Eùropie, chòc w dôwnoce rozmnôżôł sã téż w zôpadny Azji i północno-zôpadny Africe<ref>BirdLife International (2020). "Milvus milvus". IUCN Red List of Threatened Species. 2020 e.T22695072A181651010. doi:10.2305/IUCN.UK.2020-3.RLTS.T22695072A181651010.en. Retrieved 19 November 2021.</ref>. == Systematika == [[Òbrôzk:Lunak_cerveny.jpg|mały|Dozdrzeniałô kania]] Lësatô kania òstała òpisónô przez szwedzczégò przërodnika [[Karol Linneùsz|Karola Linneùsza]] w 1758 r., w dzesąti edycje jegò dokazu ''Systema Naturae'' pòd binominalną gatënkòwą pòzwą ''Falco milvus''<ref>[[Karol Linneùsz|Linneùsz, Karol]] (1758) [https://www.biodiversitylibrary.org/page/726996#page/106/mode/1up Systema naturae per regna tria naturae, secundum classes, ordines, genera, species, cum characteribus, differentiis, synonymis, locis. Tomus I. Editio decima, reformata] (w łacëznie), Tom pierszi, Vol. 1. Stockholm: Laurentius Salvius. p. 89. "F. cera flava, cauda forsicata, corpore ferrugineo, capite albidiore."</ref>. Słowò ''Milvus'' je łacyńską pòzwą na tegò ptôcha. W 1799 francësczi przëodnik Bernard Germain de Lacépède przeniósł gatënk do rodz''aju Milvus.'' Wëapartniwóné są dwa pòdgatënczi Milvus milvus * ''M. milvus milvus'' <small>(Linnaeus, 1758)</small> – '''lësatô kania.''' * ''M. milvus fasciicauda'' <small>E. Hartert, 1914</small> – '''zelonogłowskô kania''' – endemit [[Òstrowë Zelony Głowë|Òstrowów Zelony Głowë]], prawie na gwës wëmarłi. W rodzaju ''Milvus'' są téż dwa jinszé gatënczi: czôrnô kania i egipskô kania. Znóné są przëtrôfczi ùdóny hibridizacji midzë lësatima i czôrnyma kaniama. === Hibridizacjô === [[Òbrôzk:Obernberg am Inn Adlerwarte Rotmilan-0136.jpg|mały|250x250px|Lësatô kania, [[Aùstriô]]]] Rodzôj ''Milvus'' zawiérô w se jesz dwa jinszé gatënczi - czôrną kaniã i egipską kaniã<ref>Gill, Frank; Donsker, David, eds. (2018). [http://www.worldbirdnames.org/bow/raptors/ "New World vultures, Secretarybird, kites, hawks, eagles]". World Bird List Version 8.2. International Ornithologists' Union. Retrieved 24 September 2018.</ref>. Są znóné przëtrôfczi, w jaczich lësatim kanióm ùdało sã rozmnożëc z czôrnyma kaniama, dzało sã to tak w niewòli, jak i dzëczim stanie, np. na Òstrowach Zelony Głowë. Strefa hibridizacje w jaczi òba gatënczi òkazyjnie sã krziżëją, jistnieje w Centralny Eùropie.<ref>Heneberg, Petr; Dolinay, Matej; Matuší, Hynek; Pfeiffer, Thomas; Nachtigall, Winfried; Bizos, Jiri; Šimčiková, Daniela; Literák, Ivan (2016). [https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0159202 "Conservation of the Red Kite Milvus milvus (Aves: Accipitriformes) Is Not Affected by the Establishment of a Broad Hybrid Zone with the Black Kite Milvus migrans migrans in Central Europe"]. PLoS ONE. 11 (7). Retrieved 30 August 2025.</ref> Geneticzné badérowaniô kaniów z te òbrëmiô nie wëkôzałë niżódnégò [[mtDNA]] czôrnëch kaniów w lësatëch kaniach i vice versa. Przez to, że ten ôrt DNA je przekazéwóny przez nënczi, aùtorzë badaniô sùgerëją, że miészóné samice są bezpłodné zgòdą z [[Prawò Haldana|prawã Haldana]]. W 2006 rokù w Szkòcji doszło do ùdónégò rozrodu samca czôrny kanie z samicą lësati kanie w jaczim ùdało jim sã wëchòwac dwa pòdlatawce. == Wëstãpòwanié == [[Òbrôzk:Milvus milvus distr.png|lewo|mały|Apfelzynowi – lãgòwi cząd; mòdri – zëmòwanié; zelony – całoroczné wëstãpòwanié]] Lësaté kanie są ptôchama, jaczé zamieszkùją lëstowaté lasë, łączi, pòla, dolëznë i sapòwaté pògrańcza. Pòchòdzą z zôpadny [[Palearkticznô krôjna|Palearkticzny krôjnë]], tak tej wszëtczé znóné rodné parë (jaczich je kòl 32,200–37,000) młodzą sã w Eùropie. Przede wszëtczim żëje w zôpadny, westrzédny i pôłniowi Eùropie, nielëcznie téż we wschódnym parce Azji Mniészi i w północny Africe, gdze sztatus nie je gwësny - lãgòwô pòpùlacjô z Maroka je ùznôwónô za wëmarłą, chòc same kanie dali zëmùją w tim kraju. Dôwni gatënk szło pòtkac téż na Kanarijsczich Òstrowach i Òstrowach Zelony Głowë, gdze prawie na gwës wëmarł<ref>Garcia-del-Rey, E. (2011). Field Guide to the Birds of Macaronesia. Azores, Madeira, Canary Islands, Cape Verde. Lynx Edicions, Barcelona, Spain.</ref>. Kania je wãdrownô, chòc to zanôlégô òd ji pòpùlacji - ptôchë z pôłniô mieszkają ù se całi rok, a te żëjącé w ùmiarkòwónym klimace migrëją òd zélnikã do séwnikã nad [[Strzódzemné Mòrze]], abë wrócëc do swòjich gniôzd kòl strëmiannika. W przërównanim do [[Czôrnô kania|czôrny kanie]], ji syg je ògraniczony. W Pòlsce je to nielëczny lãgòwi ptôch. Kanie są pòtikóné blós na zôpadze i nordowim wschòdze kraju ([[Wiôlgôpòlskô]], [[Pòmòrskô|Pòmòrzé]], nordowi part [[Dólny Szląsk|Dólnégò Szląska]] a téż [[Warmiô]] i [[Mazurë]])<ref>Ludwik Tomiałojć, Tadeusz Stawarczyk: Awifauna Polski. Rozmieszczenie, liczebność i zmiany. Wrocław: PTPP "pro Natura", 2003, s. 203. [[:pl:Specjalna:Książki/839196261X|ISBN 83-919626-1-X.]]</ref> W zaòstałëch regionach je rzôdkô, widiwónô je òb czas migracji. Niechtërne kanie w Centralny Eùropie mògą zëmòwac kòl rakòwiszczów lub jinëch môlów bòkadnëch w jôdã, le zachòwanié to je nieregùlarné i robią to leno pòjedinczé òsobniczi<ref>Marcin Karetta: Atlas ptaków. Pascal, 2010. I[[:pl:Specjalna:Książki/9788375136555|SBN 978-83-7513-655-5.]]</ref>. == Znanczi gatënkù == [[Òbrôzk:Schwanz-Milane.jpg|mały|Ògònë kaniów: pò prawi czôrnô kania, na lewą – lësatô]] [[Òbrôzk:Red Kite (Milvus milvus) skull at the Royal Veterinary College anatomy museum.JPG|mały|Czaszka ''Milvus Milvus'']] Ptôch charakterizëje sã wëlecałą sztaturą i bestrim òpiórzenim. Jegò cało mô 60–72 cm długòscë<ref>Orta, J., Christie, D.A. & Garcia, E.F.J.: [https://www.hbw.com/species/red-kite-milvus-milvus Red Kite (Milvus milvus)]. [w:] del Hoyo, J., Elliott, A., Sargatal, J., Christie, D.A. & de Juana, E. (eds.). Handbook of the Birds of the World Alive.</ref>, a rozcygłosc jegò lecëdłów wënôszô 175–195 cm. Samce wôżą òd 800 do 1200 gramów, timczasã samice są wiãkszé i ważą midzë 1000 a 1300 gramów. Pòza różnicą we wôdze, midzë płcama ptôcha ni ma wiãkszich różniców - òbie pëłce są szlachòwno ùbarwióné – dërdzawé cało z cemniészima plecama i biôłima akcentama na lëcadłach. Ògón czerwònobruny z wiérzchù i jasnoszari òd spòdu, dosc mòcno wcãti (niżóden eùropejsczi drapieżnik ni mô tak mòcnégò wcãcô jak kania). Temù téż ògón je nôbarżi rozpòznôwalną znanką tegò ptôcha w loce na wiôldżich wësokòscach. Głowa, szëja i place òbsadzeniô skrzidłów jasnopòpielaté ze sztrichòwanim. Wiérzch cała dërdzawòbruny, spódk cała jasniészi, sztrichòwóny. Na dólny starnie skrzidła to dô wiôldżi biôłi plachc, na wiérzchny starnie przeznórtny pasyk. Żôłti dzób z czôrnym kùńcã, żółté nodżi. Rzôdkò zdôrzają sã ptôchë z [[Leùcyzm|leùcysticzną]] fòrmą. Szlachùjącô za nią czôrnô kania je përznã mniészô, mô wiele cemniészé òpiórzenié i mni wcãti ògón. W przërównanim do [[Mësznik|mësznika]] je wiele wikszô i mô dłëższé skrzidła. Pò lãgòwim czasu kanie mògą noclegòwac w karnach dochôdającëch nawetka do sta sztëk, chòc nie je tak drësznô jak czôrnô kania. Lësatô kania czãsto lecy na prostëch abò letkò zdżãtëch skrzidłach, co w sztôłce przëbacziwô lëtrã M. W chùtczim i chwatczim loce mô prawie wiedno rozkòscérzony ògón, chtëren czasã sã przekrzéwiô. Kanie żëją kòl 25 lat, chòc w 2023 we [[Wiôlgô Britaniô|Wiôldżi Britanii]] òstała nalazłô kania, chtërna dożëła 29 lat.<ref>[https://www.bbc.co.uk/news/uk-england-oxfordshire-65646200 "One of the first red kites released into Thames Valley dies"]. BBC News. 20 May 2023. Retrieved 31 July 2023.</ref> == Biotop i jôda == [[Òbrôzk:Red Kite R1 4513.jpg|mały|Lësatô kania w loce, [[Wiôlgô Britaniô]]]] === Biotop === Widné, lëstowaté i mieszóné, stôré lasë roscącé kòl łąków i pól, czãstò w òkòlim dolëznów rzék, stawów, czë skalëstëch ùbrzegòw. Pòtikónô je na nizynnëch terenach, jak téż w górach. === Jôda === Kanie są generalisticznyma drapieżnikama i scërzożércama, przez co jich dieta je tak różnorodnô, jak jich strzodowiszcze. Pòżiwiają sã môłima [[Krãgówce|krãgówcama]], przede wszëtczim drobnyma [[Sysczi|syskama]], taczima jak: [[Toczk|toczczi]], czë [[Grëzôk|grëzôczi]], ale téż [[Wieszczórka|wieszczórczi]], [[Òwôd|òwadë]] i [[Wãdzëbôk|wãdzëbôczi]]. Zdarziwô jima sã téż jesc rëbë wëszmërgniãté na sztrąd. Ptôchë te baro sprôwnie zbiérają òb czas nisczégò lotu, dzãka czemù jachtëją na swòje òfiarë. Rozmieją nawetka wëcygnąc biérno płëwającé na wòdze rebë. [[Òbrôzk:Rote Milane mit Beute.jpg|lewo|mały|Pôra kaniów z òfiarą, [[Niemieckô]]]] Òne wòlą chwacëc żëwą zdobëcz, niż jesc scërza. Zdôrzô jim sã atakòwac jinszé rabùszné ptôchë (nawetka te wiãkszé òd nich) w célu kradzeżë jichny òfiarë, czim badérowie dolmaczą nalôżóné w ji wnãtrznoscach resztczi zwiérzãt, jaczich kanie ni mògłebë ùpòlowac. Swòjich òfiarów wëzérają z gałãzów drzéwiãt, chòc òwadë i môłé sysczi rozmieje schwacëc nawetka òb czas wòlnégò chòdzeniô. Pòza tim lësaté kanie czasã plundrëją gniôzda [[Krëkòwaté|krëkòwatëch]]. == Lãgòwi cząd == [[Òbrôzk:Horst des Rotmilans (Milvus milvus).JPG|mały|Gniôzdo kani]] Kanie rozmnôżają sã, czej staną sã dwa lata stôré, chòc zdôrzają sã przëtrôfczi roczniôków, jaczim ùdało sã młodzëc<ref>Evans, I. M.; Cordero, P. J.; Parkin, D. T. (1998). "Successful breeding at one year of age by Red Kites Milvus milvus in southern England". Ibis. 140 (1): 53–57</ref>. Są to mònogamiczné ptôchë, chtërne w stałëch pòpùlacjach òstają w ti sami pôrze przez zëmã, òsoblëwò jeżlë ptôchë òstają na tim samim teritorium. Wãdrowné kanie pò zëmie wrôcają do tegò samégò gniôzda, czim pòdtrzimùją wiérnosc midzë ptôchama. === Gniôzdo === [[Òbrôzk:Milmil njg 980613.jpg|lewo|mały|Młodé kanie w gniôzdze. [[Berlëno]], [[Niemieckô]].]] Nôczãscy bùdowóné je na lëstowatim drzewie, na rozwietwienim gałãzów abò twélów, na wësokòscë midzë 12 a 15 métrów. Kanie wic wëbiérają drzewa na kraju lasa. Bùdową gniôzda zajmùją sã òbie pëłce. Samc haléwô wietwie ò długòscë 30–50 cm, a samica je ukłôdô. Pòdstawą je grëbô gałąz, scelonô mitczima roscënama, ale téż różnyma smiecama, np. papiorã, kòdrama, gnôtama czë skórama. Òba ptôchë dodają wiãcy materiałów òb czas inkùbacji i chòwaniô młodëch. Kanie jedno gniôzdô ùżiwają przez wiele lat<ref name=":0">Cramp, Stanley; et al., eds. (1980). "Milvus milvus Red Kite". Handbook of the Birds of Europe the Middle East and North Africa: The Birds of the Western Palearctic. Vol. II: Hawks to Bustards. Oxford: Oxford University Press. pp. 36–44. <nowiki>ISBN 978-0-19-857505-4</nowiki>.</ref>. Òkazjonalnie zdôrzô sã kanióm wëkòrzëstac stôré gniôzda [[Mësznik|mëszników]]. Òb czas lãgù kanie są agresywné wedle rabùsznëch ptôchów, tak tej w òkòlim jich gniôzda nie młodzą sã jinszé [[grabcaté]]. === Jaja === [[Òbrôzk:Milvus milvus MHNT.ZOO.2010.11.80.15.jpg|mały|Jaje lësati kani]] Kanie niesą sã leno rôz w rokù, midzë łżëkwiatã a majã<ref name=":0" />. Òb czas jednégò gòdowégò cządu kania niese wic midzë jednym, a trzema jajama, chòc òdnotowóné bëłë przëtrôfczi, gdze w lãgù bëło jaż piãc jôj. Jaja są biôłé z zemisto-czerwionyma kropkama. Statisticznie jaje wôżi 63 gramë i mô wëmiarë 57 mm x 47 mm.<ref name=":0" /> Jeżlë ląg òstónie stracóny, samica zôs zniese jaja.<ref name=":0" /> === Wësadowanié i dorôstanié === Jaja wësadowóné są przez kòl 30 dni przez samicã. Samc w tim czasu przënosy ji jôdã, chòc czasã zastãpùje swòją partnerkã, czej òna lecy jachtowac. Pizglãta pierszé dwa tidzenié żëcô spãdzywają w gniôzdze razã z nënką, chtërna kôrmi je jôdą halóną przez samca. Pózni òba ptôchë lôtają pò jestkù dlô młodëch. Pò 14 dniach młodé same mùszą sã karmic przëniosłim do gniôzda jedzenim. Kòl 45 dnia żëcô pizglãta zaczinają sã rãplowac na gałãze kòl gniôzda, le rzôdkò pòdlatują przed 48–50 dniã żëcô, a czasã nawetka do wiekù 60–70 dniów. Pòdlatawce spãdzywają jesz dwa do trzech tidzeniów w pòblëżim gniôzda z dokarmiającyma jich starszima. Pò dwùch latach młodé stają sã dozdrzeniałima ptôchama. == Galeriô == [[Òbrôzk:Leucistic Red Kite - Flickr - Andy Morffew.jpg|lewo|mały|Leùcysticzny òsobnik]] [[Òbrôzk:Milhafre-real, Red Kite (52539996534).jpg|centruj|mały|Lësatô kania w Pòrtugalii]] == Przëpisë == {{Przëpisë}} == Bùtnowé lënczi == {{Commons}} [[Kategòrëjô:Ptôchë]] [[Kategòrëjô:Jastrzibowaté]] [[Kategòrëjô:Ptôchë Eùropë]] [[Kategòrëjô:Rabùszné Ptôchë]] nsmnvhs9p8aa3ln934ycqxnzxulfzqk 204492 204486 2026-07-06T07:43:55Z Rabùszniczka 19710 204492 wikitext text/x-wiki '''Lësatô kania''' (''Milvus milvus'') – gatënk drapieżnégò ptôcha z rodzëznë [[Accipitridae|jastrzëbòwatëch]]. Gatënk ten dzysdnia gniôzdëje w Eùropie, chòc w dôwnoce rozmnôżôł sã téż w zôpadny Azji i północno-zôpadny Africe<ref>BirdLife International (2020). "Milvus milvus". IUCN Red List of Threatened Species. 2020 e.T22695072A181651010. doi:10.2305/IUCN.UK.2020-3.RLTS.T22695072A181651010.en. Retrieved 19 November 2021.</ref>. == Systematika == [[Òbrôzk:Lunak_cerveny.jpg|mały|Dozdrzeniałô kania]] Lësatô kania òstała òpisónô przez szwedzczégò przërodnika [[Karol Linneùsz|Karola Linneùsza]] w 1758 r., w dzesąti edycje jegò dokazu ''Systema Naturae'' pòd binominalną gatënkòwą pòzwą ''Falco milvus''<ref>[[Karol Linneùsz|Linneùsz, Karol]] (1758) [https://www.biodiversitylibrary.org/page/726996#page/106/mode/1up Systema naturae per regna tria naturae, secundum classes, ordines, genera, species, cum characteribus, differentiis, synonymis, locis. Tomus I. Editio decima, reformata] (w łacëznie), Tom pierszi, Vol. 1. Stockholm: Laurentius Salvius. p. 89. "F. cera flava, cauda forsicata, corpore ferrugineo, capite albidiore."</ref>. Słowò ''Milvus'' je łacyńską pòzwą na tegò ptôcha. W 1799 francësczi przëodnik Bernard Germain de Lacépède przeniósł gatënk do rodz''aju Milvus.'' Wëapartniwóné są dwa pòdgatënczi Milvus milvus * ''M. milvus milvus'' <small>(Linnaeus, 1758)</small> – '''lësatô kania.''' * ''M. milvus fasciicauda'' <small>E. Hartert, 1914</small> – '''zelonogłowskô kania''' – endemit [[Òstrowë Zelony Głowë|Òstrowów Zelony Głowë]], prawie na gwës wëmarłi. W rodzaju ''Milvus'' są téż dwa jinszé gatënczi: czôrnô kania i egipskô kania. Znóné są przëtrôfczi ùdóny hibridizacji midzë lësatima i czôrnyma kaniama. === Hibridizacjô === [[Òbrôzk:Obernberg am Inn Adlerwarte Rotmilan-0136.jpg|mały|250x250px|Lësatô kania, [[Aùstriô]]]] Rodzôj ''Milvus'' zawiérô w se jesz dwa jinszé gatënczi - czôrną kaniã i egipską kaniã<ref>Gill, Frank; Donsker, David, eds. (2018). [http://www.worldbirdnames.org/bow/raptors/ "New World vultures, Secretarybird, kites, hawks, eagles]". World Bird List Version 8.2. International Ornithologists' Union. Retrieved 24 September 2018.</ref>. Są znóné przëtrôfczi, w jaczich lësatim kanióm ùdało sã rozmnożëc z czôrnyma kaniama, dzało sã to tak w niewòli, jak i dzëczim stanie, np. na Òstrowach Zelony Głowë. Strefa hibridizacje w jaczi òba gatënczi òkazyjnie sã krziżëją, jistnieje w Centralny Eùropie.<ref>Heneberg, Petr; Dolinay, Matej; Matuší, Hynek; Pfeiffer, Thomas; Nachtigall, Winfried; Bizos, Jiri; Šimčiková, Daniela; Literák, Ivan (2016). [https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0159202 "Conservation of the Red Kite Milvus milvus (Aves: Accipitriformes) Is Not Affected by the Establishment of a Broad Hybrid Zone with the Black Kite Milvus migrans migrans in Central Europe"]. PLoS ONE. 11 (7). Retrieved 30 August 2025.</ref> Geneticzné badérowaniô kaniów z te òbrëmiô nie wëkôzałë niżódnégò [[mtDNA]] czôrnëch kaniów w lësatëch kaniach i vice versa. Przez to, że ten ôrt DNA je przekazéwóny przez nënczi, aùtorzë badaniô sùgerëją, że miészóné samice są bezpłodné zgòdą z [[Prawò Haldana|prawã Haldana]]. W 2006 rokù w Szkòcji doszło do ùdónégò rozrodu samca czôrny kanie z samicą lësati kanie w jaczim ùdało jim sã wëchòwac dwa pòdlatawce. == Wëstãpòwanié == [[Òbrôzk:Milvus milvus distr.png|lewo|mały|Apfelzynowi – lãgòwi cząd; mòdri – zëmòwanié; zelony – całoroczné wëstãpòwanié]] Lësaté kanie są ptôchama, jaczé zamieszkùją lëstowaté lasë, łączi, pòla, dolëznë i sapòwaté pògrańcza. Pòchòdzą z zôpadny [[Palearkticznô krôjna|Palearkticzny krôjnë]], tak tej wszëtczé znóné rodné parë (jaczich je kòl 32,200–37,000) młodzą sã w Eùropie. Przede wszëtczim żëje w zôpadny, westrzédny i pôłniowi Eùropie, nielëcznie téż we wschódnym parce Azji Mniészi i w północny Africe, gdze sztatus nie je gwësny - lãgòwô pòpùlacjô z Maroka je ùznôwónô za wëmarłą, chòc same kanie dali zëmùją w tim kraju. Dôwni gatënk szło pòtkac téż na Kanarijsczich Òstrowach i Òstrowach Zelony Głowë, gdze prawie na gwës wëmarł<ref>Garcia-del-Rey, E. (2011). Field Guide to the Birds of Macaronesia. Azores, Madeira, Canary Islands, Cape Verde. Lynx Edicions, Barcelona, Spain.</ref>. Kania je wãdrownô, chòc to zanôlégô òd ji pòpùlacji - ptôchë z pôłniô mieszkają ù se całi rok, a te żëjącé w ùmiarkòwónym klimace migrëją òd zélnikã do séwnikã nad [[Strzódzemné Mòrze]], abë wrócëc do swòjich gniôzd kòl strëmiannika. W przërównanim do [[Czôrnô kania|czôrny kanie]], ji syg je ògraniczony. W Pòlsce je to nielëczny lãgòwi ptôch. Kanie są pòtikóné blós na zôpadze i nordowim wschòdze kraju ([[Wiôlgôpòlskô]], [[Pòmòrskô|Pòmòrzé]], nordowi part [[Dólny Szląsk|Dólnégò Szląska]] a téż [[Warmiô]] i [[Mazurë]])<ref>Ludwik Tomiałojć, Tadeusz Stawarczyk: Awifauna Polski. Rozmieszczenie, liczebność i zmiany. Wrocław: PTPP "pro Natura", 2003, s. 203. [[:pl:Specjalna:Książki/839196261X|ISBN 83-919626-1-X.]]</ref> W zaòstałëch regionach je rzôdkô, widiwónô je òb czas migracji. Niechtërne kanie w Centralny Eùropie mògą zëmòwac kòl rakòwiszczów lub jinëch môlów bòkadnëch w jôdã, le zachòwanié to je nieregùlarné i robią to leno pòjedinczé òsobniczi<ref>Marcin Karetta: Atlas ptaków. Pascal, 2010. I[[:pl:Specjalna:Książki/9788375136555|SBN 978-83-7513-655-5.]]</ref>. == Znanczi gatënkù == [[Òbrôzk:Schwanz-Milane.jpg|mały|Ògònë kaniów: pò prawi czôrnô kania, na lewą – lësatô]] [[Òbrôzk:Red Kite (Milvus milvus) skull at the Royal Veterinary College anatomy museum.JPG|mały|Czaszka ''Milvus Milvus'']] Ptôch charakterizëje sã wëlecałą sztaturą i bestrim òpiórzenim. Jegò cało mô 60–72 cm długòscë<ref>Orta, J., Christie, D.A. & Garcia, E.F.J.: [https://www.hbw.com/species/red-kite-milvus-milvus Red Kite (Milvus milvus)]. [w:] del Hoyo, J., Elliott, A., Sargatal, J., Christie, D.A. & de Juana, E. (eds.). Handbook of the Birds of the World Alive.</ref>, a rozcygłosc jegò lecëdłów wënôszô 175–195 cm. Samce wôżą òd 800 do 1200 gramów, timczasã samice są wiãkszé i ważą midzë 1000 a 1300 gramów. Pòza różnicą we wôdze, midzë płcama ptôcha ni ma wiãkszich różniców - òbie pëłce są szlachòwno ùbarwióné – dërdzawé cało z cemniészima plecama i biôłima akcentama na lëcadłach. Ògón czerwònobruny z wiérzchù i jasnoszari òd spòdu, dosc mòcno wcãti (niżóden eùropejsczi drapieżnik ni mô tak mòcnégò wcãcô jak kania). Temù téż ògón je nôbarżi rozpòznôwalną znanką tegò ptôcha w loce na wiôldżich wësokòscach. Głowa, szëja i place òbsadzeniô skrzidłów jasnopòpielaté ze sztrichòwanim. Wiérzch cała dërdzawòbruny, spódk cała jasniészi, sztrichòwóny. Na dólny starnie skrzidła to dô wiôldżi biôłi plachc, na wiérzchny starnie przeznórtny pasyk. Żôłti dzób z czôrnym kùńcã, żółté nodżi. Rzôdkò zdôrzają sã ptôchë z [[Leùcyzm|leùcysticzną]] fòrmą. Szlachùjącô za nią czôrnô kania je përznã mniészô, mô wiele cemniészé òpiórzenié i mni wcãti ògón. W przërównanim do [[Mësznik|mësznika]] je wiele wikszô i mô dłëższé skrzidła. Pò lãgòwim czasu kanie mògą noclegòwac w karnach dochôdającëch nawetka do sta sztëk, chòc nie je tak drësznô jak czôrnô kania. Lësatô kania czãsto lecy na prostëch abò letkò zdżãtëch skrzidłach, co w sztôłce przëbacziwô lëtrã M. W chùtczim i chwatczim loce mô prawie wiedno rozkòscérzony ògón, chtëren czasã sã przekrzéwiô. Kanie żëją kòl 25 lat, chòc w 2023 we [[Wiôlgô Britaniô|Wiôldżi Britanii]] òstała nalazłô kania, chtërna dożëła 29 lat.<ref>[https://www.bbc.co.uk/news/uk-england-oxfordshire-65646200 "One of the first red kites released into Thames Valley dies"]. BBC News. 20 May 2023. Retrieved 31 July 2023.</ref> == Biotop i jôda == [[Òbrôzk:Red Kite R1 4513.jpg|mały|Lësatô kania w loce, [[Wiôlgô Britaniô]]]] === Biotop === Widné, lëstowaté i mieszóné, stôré lasë roscącé kòl łąków i pól, czãstò w òkòlim dolëznów rzék, stawów, czë skalëstëch ùbrzegòw. Pòtikónô je na nizynnëch terenach, jak téż w górach. === Jôda === Kanie są generalisticznyma drapieżnikama i scërzożércama, przez co jich dieta je tak różnorodnô, jak jich strzodowiszcze. Pòżiwiają sã môłima [[Krãgówce|krãgówcama]], przede wszëtczim drobnyma [[Sysczi|syskama]], taczima jak: [[Toczk|toczczi]], czë [[Grëzôk|grëzôczi]], ale téż [[Wieszczórka|wieszczórczi]], [[Òwôd|òwadë]] i [[Wãdzëbôk|wãdzëbôczi]]. Zdarziwô jima sã téż jesc rëbë wëszmërgniãté na sztrąd. Ptôchë te baro sprôwnie zbiérają òb czas nisczégò lotu, dzãka czemù jachtëją na swòje òfiarë. Rozmieją nawetka wëcygnąc biérno płëwającé na wòdze rebë. [[Òbrôzk:Rote Milane mit Beute.jpg|lewo|mały|Pôra kaniów z òfiarą, [[Niemieckô]]]] Òne wòlą chwacëc żëwą zdobëcz, niż jesc scërza. Zdôrzô jim sã atakòwac jinszé rabùszné ptôchë (nawetka te wiãkszé òd nich) w célu kradzeżë jichny òfiarë, czim badérowie dolmaczą nalôżóné w ji wnãtrznoscach resztczi zwiérzãt, jaczich kanie ni mògłebë ùpòlowac. Swòjich òfiarów wëzérają z gałãzów drzéwiãt, chòc òwadë i môłé sysczi rozmieje schwacëc nawetka òb czas wòlnégò chòdzeniô. Pòza tim lësaté kanie czasã plundrëją gniôzda [[Krëkòwaté|krëkòwatëch]]. == Lãgòwi cząd == [[Òbrôzk:Horst des Rotmilans (Milvus milvus).JPG|mały|Gniôzdo kani]] Kanie rozmnôżają sã, czej staną sã dwa lata stôré, chòc zdôrzają sã przëtrôfczi roczniôków, jaczim ùdało sã młodzëc<ref>Evans, I. M.; Cordero, P. J.; Parkin, D. T. (1998). "Successful breeding at one year of age by Red Kites Milvus milvus in southern England". Ibis. 140 (1): 53–57</ref>. Są to mònogamiczné ptôchë, chtërne w stałëch pòpùlacjach òstają w ti sami pôrze przez zëmã, òsoblëwò jeżlë ptôchë òstają na tim samim teritorium. Wãdrowné kanie pò zëmie wrôcają do tegò samégò gniôzda, czim pòdtrzimùją wiérnosc midzë ptôchama. === Gniôzdo === [[Òbrôzk:Milmil njg 980613.jpg|lewo|mały|Młodé kanie w gniôzdze. [[Berlëno]], [[Niemieckô]].]] Nôczãscy bùdowóné je na lëstowatim drzewie, na rozwietwienim gałãzów abò twélów, na wësokòscë midzë 12 a 15 métrów. Kanie wic wëbiérają drzewa na kraju lasa. Bùdową gniôzda zajmùją sã òbie pëłce. Samc haléwô wietwie ò długòscë 30–50 cm, a samica je ukłôdô. Pòdstawą je grëbô gałąz, scelonô mitczima roscënama, ale téż różnyma smiecama, np. papiorã, kòdrama, gnôtama czë skórama. Òba ptôchë dodają wiãcy materiałów òb czas inkùbacji i chòwaniô młodëch. Kanie jedno gniôzdô ùżiwają przez wiele lat<ref name=":0">Cramp, Stanley; et al., eds. (1980). "Milvus milvus Red Kite". Handbook of the Birds of Europe the Middle East and North Africa: The Birds of the Western Palearctic. Vol. II: Hawks to Bustards. Oxford: Oxford University Press. pp. 36–44. <nowiki>ISBN 978-0-19-857505-4</nowiki>.</ref>. Òkazjonalnie zdôrzô sã kanióm wëkòrzëstac stôré gniôzda [[Mësznik|mëszników]]. Òb czas lãgù kanie są agresywné wedle rabùsznëch ptôchów, tak tej w òkòlim jich gniôzda nie młodzą sã jinszé [[grabcaté]]. === Jaja === [[Òbrôzk:Milvus milvus MHNT.ZOO.2010.11.80.15.jpg|mały|Jaje lësati kani]] Kanie niesą sã leno rôz w rokù, midzë łżëkwiatã a majã<ref name=":0" />. Òb czas jednégò gòdowégò cządu kania niese wic midzë jednym, a trzema jajama, chòc òdnotowóné bëłë przëtrôfczi, gdze w lãgù bëło jaż piãc jôj. Jaja są biôłé z zemisto-czerwionyma kropkama. Statisticznie jaje wôżi 63 gramë i mô wëmiarë 57 mm x 47 mm.<ref name=":0" /> Jeżlë ląg òstónie stracóny, samica zôs zniese jaja.<ref name=":0" /> === Wësadowanié i dorôstanié === Jaja wësadowóné są przez kòl 30 dni przez samicã. Samc w tim czasu przënosy ji jôdã, chòc czasã zastãpùje swòją partnerkã, czej òna lecy jachtowac. Pizglãta pierszé dwa tidzenié żëcô spãdzywają w gniôzdze razã z nënką, chtërna kôrmi je jôdą halóną przez samca. Pózni òba ptôchë lôtają pò jestkù dlô młodëch. Pò 14 dniach młodé same mùszą sã karmic przëniosłim do gniôzda jedzenim. Kòl 45 dnia żëcô pizglãta zaczinają sã rãplowac na gałãze kòl gniôzda, le rzôdkò pòdlatują przed 48–50 dniã żëcô, a czasã nawetka do wiekù 60–70 dniów. Pòdlatawce spãdzywają jesz dwa do trzech tidzeniów w pòblëżim gniôzda z dokarmiającyma jich starszima. Pò dwùch latach młodé stają sã dozdrzeniałima ptôchama. == Galeriô == [[Òbrôzk:Leucistic Red Kite - Flickr - Andy Morffew.jpg|lewo|mały|Leùcysticzny òsobnik]] [[Òbrôzk:Milhafre-real, Red Kite (52539996534).jpg|centruj|mały|Lësatô kania w Pòrtugalii]] == Przëpisë == {{Przëpisë}} == Bùtnowé lënczi == {{Commons}} [[Kategòrëjô:Ptôchë]] [[Kategòrëjô:Jastrzibowaté]] [[Kategòrëjô:Ptôchë Eùropë]] [[Kategòrëjô:Rabùszné Ptôchë]] sr0wg6mtbt6o9m42be56xne6g2jpatu 204498 204492 2026-07-06T11:16:10Z Iketsi 3254 Galeriô 204498 wikitext text/x-wiki '''Lësatô kania''' (''Milvus milvus'') – gatënk drapieżnégò ptôcha z rodzëznë [[Accipitridae|jastrzëbòwatëch]]. Gatënk ten dzysdnia gniôzdëje w Eùropie, chòc w dôwnoce rozmnôżôł sã téż w zôpadny Azji i północno-zôpadny Africe<ref>BirdLife International (2020). "Milvus milvus". IUCN Red List of Threatened Species. 2020 e.T22695072A181651010. doi:10.2305/IUCN.UK.2020-3.RLTS.T22695072A181651010.en. Retrieved 19 November 2021.</ref>. == Systematika == [[Òbrôzk:Lunak_cerveny.jpg|mały|Dozdrzeniałô kania]] Lësatô kania òstała òpisónô przez szwedzczégò przërodnika [[Karol Linneùsz|Karola Linneùsza]] w 1758 r., w dzesąti edycje jegò dokazu ''Systema Naturae'' pòd binominalną gatënkòwą pòzwą ''Falco milvus''<ref>[[Karol Linneùsz|Linneùsz, Karol]] (1758) [https://www.biodiversitylibrary.org/page/726996#page/106/mode/1up Systema naturae per regna tria naturae, secundum classes, ordines, genera, species, cum characteribus, differentiis, synonymis, locis. Tomus I. Editio decima, reformata] (w łacëznie), Tom pierszi, Vol. 1. Stockholm: Laurentius Salvius. p. 89. "F. cera flava, cauda forsicata, corpore ferrugineo, capite albidiore."</ref>. Słowò ''Milvus'' je łacyńską pòzwą na tegò ptôcha. W 1799 francësczi przëodnik Bernard Germain de Lacépède przeniósł gatënk do rodz''aju Milvus.'' Wëapartniwóné są dwa pòdgatënczi Milvus milvus * ''M. milvus milvus'' <small>(Linnaeus, 1758)</small> – '''lësatô kania.''' * ''M. milvus fasciicauda'' <small>E. Hartert, 1914</small> – '''zelonogłowskô kania''' – endemit [[Òstrowë Zelony Głowë|Òstrowów Zelony Głowë]], prawie na gwës wëmarłi. W rodzaju ''Milvus'' są téż dwa jinszé gatënczi: czôrnô kania i egipskô kania. Znóné są przëtrôfczi ùdóny hibridizacji midzë lësatima i czôrnyma kaniama. === Hibridizacjô === [[Òbrôzk:Obernberg am Inn Adlerwarte Rotmilan-0136.jpg|mały|250x250px|Lësatô kania, [[Aùstriô]]]] Rodzôj ''Milvus'' zawiérô w se jesz dwa jinszé gatënczi - czôrną kaniã i egipską kaniã<ref>Gill, Frank; Donsker, David, eds. (2018). [http://www.worldbirdnames.org/bow/raptors/ "New World vultures, Secretarybird, kites, hawks, eagles]". World Bird List Version 8.2. International Ornithologists' Union. Retrieved 24 September 2018.</ref>. Są znóné przëtrôfczi, w jaczich lësatim kanióm ùdało sã rozmnożëc z czôrnyma kaniama, dzało sã to tak w niewòli, jak i dzëczim stanie, np. na Òstrowach Zelony Głowë. Strefa hibridizacje w jaczi òba gatënczi òkazyjnie sã krziżëją, jistnieje w Centralny Eùropie.<ref>Heneberg, Petr; Dolinay, Matej; Matuší, Hynek; Pfeiffer, Thomas; Nachtigall, Winfried; Bizos, Jiri; Šimčiková, Daniela; Literák, Ivan (2016). [https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0159202 "Conservation of the Red Kite Milvus milvus (Aves: Accipitriformes) Is Not Affected by the Establishment of a Broad Hybrid Zone with the Black Kite Milvus migrans migrans in Central Europe"]. PLoS ONE. 11 (7). Retrieved 30 August 2025.</ref> Geneticzné badérowaniô kaniów z te òbrëmiô nie wëkôzałë niżódnégò [[mtDNA]] czôrnëch kaniów w lësatëch kaniach i vice versa. Przez to, że ten ôrt DNA je przekazéwóny przez nënczi, aùtorzë badaniô sùgerëją, że miészóné samice są bezpłodné zgòdą z [[Prawò Haldana|prawã Haldana]]. W 2006 rokù w Szkòcji doszło do ùdónégò rozrodu samca czôrny kanie z samicą lësati kanie w jaczim ùdało jim sã wëchòwac dwa pòdlatawce. == Wëstãpòwanié == [[Òbrôzk:Milvus milvus distr.png|lewo|mały|Apfelzynowi – lãgòwi cząd; mòdri – zëmòwanié; zelony – całoroczné wëstãpòwanié]] Lësaté kanie są ptôchama, jaczé zamieszkùją lëstowaté lasë, łączi, pòla, dolëznë i sapòwaté pògrańcza. Pòchòdzą z zôpadny [[Palearkticznô krôjna|Palearkticzny krôjnë]], tak tej wszëtczé znóné rodné parë (jaczich je kòl 32,200–37,000) młodzą sã w Eùropie. Przede wszëtczim żëje w zôpadny, westrzédny i pôłniowi Eùropie, nielëcznie téż we wschódnym parce Azji Mniészi i w północny Africe, gdze sztatus nie je gwësny - lãgòwô pòpùlacjô z Maroka je ùznôwónô za wëmarłą, chòc same kanie dali zëmùją w tim kraju. Dôwni gatënk szło pòtkac téż na Kanarijsczich Òstrowach i Òstrowach Zelony Głowë, gdze prawie na gwës wëmarł<ref>Garcia-del-Rey, E. (2011). Field Guide to the Birds of Macaronesia. Azores, Madeira, Canary Islands, Cape Verde. Lynx Edicions, Barcelona, Spain.</ref>. Kania je wãdrownô, chòc to zanôlégô òd ji pòpùlacji - ptôchë z pôłniô mieszkają ù se całi rok, a te żëjącé w ùmiarkòwónym klimace migrëją òd zélnikã do séwnikã nad [[Strzódzemné Mòrze]], abë wrócëc do swòjich gniôzd kòl strëmiannika. W przërównanim do [[Czôrnô kania|czôrny kanie]], ji syg je ògraniczony. W Pòlsce je to nielëczny lãgòwi ptôch. Kanie są pòtikóné blós na zôpadze i nordowim wschòdze kraju ([[Wiôlgôpòlskô]], [[Pòmòrskô|Pòmòrzé]], nordowi part [[Dólny Szląsk|Dólnégò Szląska]] a téż [[Warmiô]] i [[Mazurë]])<ref>Ludwik Tomiałojć, Tadeusz Stawarczyk: Awifauna Polski. Rozmieszczenie, liczebność i zmiany. Wrocław: PTPP "pro Natura", 2003, s. 203. [[:pl:Specjalna:Książki/839196261X|ISBN 83-919626-1-X.]]</ref> W zaòstałëch regionach je rzôdkô, widiwónô je òb czas migracji. Niechtërne kanie w Centralny Eùropie mògą zëmòwac kòl rakòwiszczów lub jinëch môlów bòkadnëch w jôdã, le zachòwanié to je nieregùlarné i robią to leno pòjedinczé òsobniczi<ref>Marcin Karetta: Atlas ptaków. Pascal, 2010. I[[:pl:Specjalna:Książki/9788375136555|SBN 978-83-7513-655-5.]]</ref>. == Znanczi gatënkù == [[Òbrôzk:Schwanz-Milane.jpg|mały|Ògònë kaniów: pò prawi czôrnô kania, na lewą – lësatô]] [[Òbrôzk:Red Kite (Milvus milvus) skull at the Royal Veterinary College anatomy museum.JPG|mały|Czaszka ''Milvus Milvus'']] Ptôch charakterizëje sã wëlecałą sztaturą i bestrim òpiórzenim. Jegò cało mô 60–72 cm długòscë<ref>Orta, J., Christie, D.A. & Garcia, E.F.J.: [https://www.hbw.com/species/red-kite-milvus-milvus Red Kite (Milvus milvus)]. [w:] del Hoyo, J., Elliott, A., Sargatal, J., Christie, D.A. & de Juana, E. (eds.). Handbook of the Birds of the World Alive.</ref>, a rozcygłosc jegò lecëdłów wënôszô 175–195 cm. Samce wôżą òd 800 do 1200 gramów, timczasã samice są wiãkszé i ważą midzë 1000 a 1300 gramów. Pòza różnicą we wôdze, midzë płcama ptôcha ni ma wiãkszich różniców - òbie pëłce są szlachòwno ùbarwióné – dërdzawé cało z cemniészima plecama i biôłima akcentama na lëcadłach. Ògón czerwònobruny z wiérzchù i jasnoszari òd spòdu, dosc mòcno wcãti (niżóden eùropejsczi drapieżnik ni mô tak mòcnégò wcãcô jak kania). Temù téż ògón je nôbarżi rozpòznôwalną znanką tegò ptôcha w loce na wiôldżich wësokòscach. Głowa, szëja i place òbsadzeniô skrzidłów jasnopòpielaté ze sztrichòwanim. Wiérzch cała dërdzawòbruny, spódk cała jasniészi, sztrichòwóny. Na dólny starnie skrzidła to dô wiôldżi biôłi plachc, na wiérzchny starnie przeznórtny pasyk. Żôłti dzób z czôrnym kùńcã, żółté nodżi. Rzôdkò zdôrzają sã ptôchë z [[Leùcyzm|leùcysticzną]] fòrmą. Szlachùjącô za nią czôrnô kania je përznã mniészô, mô wiele cemniészé òpiórzenié i mni wcãti ògón. W przërównanim do [[Mësznik|mësznika]] je wiele wikszô i mô dłëższé skrzidła. Pò lãgòwim czasu kanie mògą noclegòwac w karnach dochôdającëch nawetka do sta sztëk, chòc nie je tak drësznô jak czôrnô kania. Lësatô kania czãsto lecy na prostëch abò letkò zdżãtëch skrzidłach, co w sztôłce przëbacziwô lëtrã M. W chùtczim i chwatczim loce mô prawie wiedno rozkòscérzony ògón, chtëren czasã sã przekrzéwiô. Kanie żëją kòl 25 lat, chòc w 2023 we [[Wiôlgô Britaniô|Wiôldżi Britanii]] òstała nalazłô kania, chtërna dożëła 29 lat.<ref>[https://www.bbc.co.uk/news/uk-england-oxfordshire-65646200 "One of the first red kites released into Thames Valley dies"]. BBC News. 20 May 2023. Retrieved 31 July 2023.</ref> == Biotop i jôda == [[Òbrôzk:Red Kite R1 4513.jpg|mały|Lësatô kania w loce, [[Wiôlgô Britaniô]]]] === Biotop === Widné, lëstowaté i mieszóné, stôré lasë roscącé kòl łąków i pól, czãstò w òkòlim dolëznów rzék, stawów, czë skalëstëch ùbrzegòw. Pòtikónô je na nizynnëch terenach, jak téż w górach. === Jôda === Kanie są generalisticznyma drapieżnikama i scërzożércama, przez co jich dieta je tak różnorodnô, jak jich strzodowiszcze. Pòżiwiają sã môłima [[Krãgówce|krãgówcama]], przede wszëtczim drobnyma [[Sysczi|syskama]], taczima jak: [[Toczk|toczczi]], czë [[Grëzôk|grëzôczi]], ale téż [[Wieszczórka|wieszczórczi]], [[Òwôd|òwadë]] i [[Wãdzëbôk|wãdzëbôczi]]. Zdarziwô jima sã téż jesc rëbë wëszmërgniãté na sztrąd. Ptôchë te baro sprôwnie zbiérają òb czas nisczégò lotu, dzãka czemù jachtëją na swòje òfiarë. Rozmieją nawetka wëcygnąc biérno płëwającé na wòdze rebë. [[Òbrôzk:Rote Milane mit Beute.jpg|lewo|mały|Pôra kaniów z òfiarą, [[Niemieckô]]]] Òne wòlą chwacëc żëwą zdobëcz, niż jesc scërza. Zdôrzô jim sã atakòwac jinszé rabùszné ptôchë (nawetka te wiãkszé òd nich) w célu kradzeżë jichny òfiarë, czim badérowie dolmaczą nalôżóné w ji wnãtrznoscach resztczi zwiérzãt, jaczich kanie ni mògłebë ùpòlowac. Swòjich òfiarów wëzérają z gałãzów drzéwiãt, chòc òwadë i môłé sysczi rozmieje schwacëc nawetka òb czas wòlnégò chòdzeniô. Pòza tim lësaté kanie czasã plundrëją gniôzda [[Krëkòwaté|krëkòwatëch]]. == Lãgòwi cząd == [[Òbrôzk:Horst des Rotmilans (Milvus milvus).JPG|mały|Gniôzdo kani]] Kanie rozmnôżają sã, czej staną sã dwa lata stôré, chòc zdôrzają sã przëtrôfczi roczniôków, jaczim ùdało sã młodzëc<ref>Evans, I. M.; Cordero, P. J.; Parkin, D. T. (1998). "Successful breeding at one year of age by Red Kites Milvus milvus in southern England". Ibis. 140 (1): 53–57</ref>. Są to mònogamiczné ptôchë, chtërne w stałëch pòpùlacjach òstają w ti sami pôrze przez zëmã, òsoblëwò jeżlë ptôchë òstają na tim samim teritorium. Wãdrowné kanie pò zëmie wrôcają do tegò samégò gniôzda, czim pòdtrzimùją wiérnosc midzë ptôchama. === Gniôzdo === [[Òbrôzk:Milmil njg 980613.jpg|lewo|mały|Młodé kanie w gniôzdze. [[Berlëno]], [[Niemieckô]].]] Nôczãscy bùdowóné je na lëstowatim drzewie, na rozwietwienim gałãzów abò twélów, na wësokòscë midzë 12 a 15 métrów. Kanie wic wëbiérają drzewa na kraju lasa. Bùdową gniôzda zajmùją sã òbie pëłce. Samc haléwô wietwie ò długòscë 30–50 cm, a samica je ukłôdô. Pòdstawą je grëbô gałąz, scelonô mitczima roscënama, ale téż różnyma smiecama, np. papiorã, kòdrama, gnôtama czë skórama. Òba ptôchë dodają wiãcy materiałów òb czas inkùbacji i chòwaniô młodëch. Kanie jedno gniôzdô ùżiwają przez wiele lat<ref name=":0">Cramp, Stanley; et al., eds. (1980). "Milvus milvus Red Kite". Handbook of the Birds of Europe the Middle East and North Africa: The Birds of the Western Palearctic. Vol. II: Hawks to Bustards. Oxford: Oxford University Press. pp. 36–44. <nowiki>ISBN 978-0-19-857505-4</nowiki>.</ref>. Òkazjonalnie zdôrzô sã kanióm wëkòrzëstac stôré gniôzda [[Mësznik|mëszników]]. Òb czas lãgù kanie są agresywné wedle rabùsznëch ptôchów, tak tej w òkòlim jich gniôzda nie młodzą sã jinszé [[grabcaté]]. === Jaja === [[Òbrôzk:Milvus milvus MHNT.ZOO.2010.11.80.15.jpg|mały|Jaje lësati kani]] Kanie niesą sã leno rôz w rokù, midzë łżëkwiatã a majã<ref name=":0" />. Òb czas jednégò gòdowégò cządu kania niese wic midzë jednym, a trzema jajama, chòc òdnotowóné bëłë przëtrôfczi, gdze w lãgù bëło jaż piãc jôj. Jaja są biôłé z zemisto-czerwionyma kropkama. Statisticznie jaje wôżi 63 gramë i mô wëmiarë 57 mm x 47 mm.<ref name=":0" /> Jeżlë ląg òstónie stracóny, samica zôs zniese jaja.<ref name=":0" /> === Wësadowanié i dorôstanié === Jaja wësadowóné są przez kòl 30 dni przez samicã. Samc w tim czasu przënosy ji jôdã, chòc czasã zastãpùje swòją partnerkã, czej òna lecy jachtowac. Pizglãta pierszé dwa tidzenié żëcô spãdzywają w gniôzdze razã z nënką, chtërna kôrmi je jôdą halóną przez samca. Pózni òba ptôchë lôtają pò jestkù dlô młodëch. Pò 14 dniach młodé same mùszą sã karmic przëniosłim do gniôzda jedzenim. Kòl 45 dnia żëcô pizglãta zaczinają sã rãplowac na gałãze kòl gniôzda, le rzôdkò pòdlatują przed 48–50 dniã żëcô, a czasã nawetka do wiekù 60–70 dniów. Pòdlatawce spãdzywają jesz dwa do trzech tidzeniów w pòblëżim gniôzda z dokarmiającyma jich starszima. Pò dwùch latach młodé stają sã dozdrzeniałima ptôchama. == Galeriô == Òbrôzk:Leucistic Red Kite - Flickr - Andy Morffew.jpg|Leùcysticzny òsobnik Òbrôzk:Milhafre-real, Red Kite (52539996534).jpg|Lësatô kania w Pòrtugalii == Przëpisë == {{Przëpisë}} == Bùtnowé lënczi == {{Commons}} [[Kategòrëjô:Ptôchë]] [[Kategòrëjô:Jastrzibowaté]] [[Kategòrëjô:Ptôchë Eùropë]] [[Kategòrëjô:Rabùszné Ptôchë]] 0y9qa7d122r52829buax827xt6bb2cw 204499 204498 2026-07-06T11:16:26Z Iketsi 3254 {{Kòntrola}} 204499 wikitext text/x-wiki '''Lësatô kania''' (''Milvus milvus'') – gatënk drapieżnégò ptôcha z rodzëznë [[Accipitridae|jastrzëbòwatëch]]. Gatënk ten dzysdnia gniôzdëje w Eùropie, chòc w dôwnoce rozmnôżôł sã téż w zôpadny Azji i północno-zôpadny Africe<ref>BirdLife International (2020). "Milvus milvus". IUCN Red List of Threatened Species. 2020 e.T22695072A181651010. doi:10.2305/IUCN.UK.2020-3.RLTS.T22695072A181651010.en. Retrieved 19 November 2021.</ref>. == Systematika == [[Òbrôzk:Lunak_cerveny.jpg|mały|Dozdrzeniałô kania]] Lësatô kania òstała òpisónô przez szwedzczégò przërodnika [[Karol Linneùsz|Karola Linneùsza]] w 1758 r., w dzesąti edycje jegò dokazu ''Systema Naturae'' pòd binominalną gatënkòwą pòzwą ''Falco milvus''<ref>[[Karol Linneùsz|Linneùsz, Karol]] (1758) [https://www.biodiversitylibrary.org/page/726996#page/106/mode/1up Systema naturae per regna tria naturae, secundum classes, ordines, genera, species, cum characteribus, differentiis, synonymis, locis. Tomus I. Editio decima, reformata] (w łacëznie), Tom pierszi, Vol. 1. Stockholm: Laurentius Salvius. p. 89. "F. cera flava, cauda forsicata, corpore ferrugineo, capite albidiore."</ref>. Słowò ''Milvus'' je łacyńską pòzwą na tegò ptôcha. W 1799 francësczi przëodnik Bernard Germain de Lacépède przeniósł gatënk do rodz''aju Milvus.'' Wëapartniwóné są dwa pòdgatënczi Milvus milvus * ''M. milvus milvus'' <small>(Linnaeus, 1758)</small> – '''lësatô kania.''' * ''M. milvus fasciicauda'' <small>E. Hartert, 1914</small> – '''zelonogłowskô kania''' – endemit [[Òstrowë Zelony Głowë|Òstrowów Zelony Głowë]], prawie na gwës wëmarłi. W rodzaju ''Milvus'' są téż dwa jinszé gatënczi: czôrnô kania i egipskô kania. Znóné są przëtrôfczi ùdóny hibridizacji midzë lësatima i czôrnyma kaniama. === Hibridizacjô === [[Òbrôzk:Obernberg am Inn Adlerwarte Rotmilan-0136.jpg|mały|250x250px|Lësatô kania, [[Aùstriô]]]] Rodzôj ''Milvus'' zawiérô w se jesz dwa jinszé gatënczi - czôrną kaniã i egipską kaniã<ref>Gill, Frank; Donsker, David, eds. (2018). [http://www.worldbirdnames.org/bow/raptors/ "New World vultures, Secretarybird, kites, hawks, eagles]". World Bird List Version 8.2. International Ornithologists' Union. Retrieved 24 September 2018.</ref>. Są znóné przëtrôfczi, w jaczich lësatim kanióm ùdało sã rozmnożëc z czôrnyma kaniama, dzało sã to tak w niewòli, jak i dzëczim stanie, np. na Òstrowach Zelony Głowë. Strefa hibridizacje w jaczi òba gatënczi òkazyjnie sã krziżëją, jistnieje w Centralny Eùropie.<ref>Heneberg, Petr; Dolinay, Matej; Matuší, Hynek; Pfeiffer, Thomas; Nachtigall, Winfried; Bizos, Jiri; Šimčiková, Daniela; Literák, Ivan (2016). [https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0159202 "Conservation of the Red Kite Milvus milvus (Aves: Accipitriformes) Is Not Affected by the Establishment of a Broad Hybrid Zone with the Black Kite Milvus migrans migrans in Central Europe"]. PLoS ONE. 11 (7). Retrieved 30 August 2025.</ref> Geneticzné badérowaniô kaniów z te òbrëmiô nie wëkôzałë niżódnégò [[mtDNA]] czôrnëch kaniów w lësatëch kaniach i vice versa. Przez to, że ten ôrt DNA je przekazéwóny przez nënczi, aùtorzë badaniô sùgerëją, że miészóné samice są bezpłodné zgòdą z [[Prawò Haldana|prawã Haldana]]. W 2006 rokù w Szkòcji doszło do ùdónégò rozrodu samca czôrny kanie z samicą lësati kanie w jaczim ùdało jim sã wëchòwac dwa pòdlatawce. == Wëstãpòwanié == [[Òbrôzk:Milvus milvus distr.png|lewo|mały|Apfelzynowi – lãgòwi cząd; mòdri – zëmòwanié; zelony – całoroczné wëstãpòwanié]] Lësaté kanie są ptôchama, jaczé zamieszkùją lëstowaté lasë, łączi, pòla, dolëznë i sapòwaté pògrańcza. Pòchòdzą z zôpadny [[Palearkticznô krôjna|Palearkticzny krôjnë]], tak tej wszëtczé znóné rodné parë (jaczich je kòl 32,200–37,000) młodzą sã w Eùropie. Przede wszëtczim żëje w zôpadny, westrzédny i pôłniowi Eùropie, nielëcznie téż we wschódnym parce Azji Mniészi i w północny Africe, gdze sztatus nie je gwësny - lãgòwô pòpùlacjô z Maroka je ùznôwónô za wëmarłą, chòc same kanie dali zëmùją w tim kraju. Dôwni gatënk szło pòtkac téż na Kanarijsczich Òstrowach i Òstrowach Zelony Głowë, gdze prawie na gwës wëmarł<ref>Garcia-del-Rey, E. (2011). Field Guide to the Birds of Macaronesia. Azores, Madeira, Canary Islands, Cape Verde. Lynx Edicions, Barcelona, Spain.</ref>. Kania je wãdrownô, chòc to zanôlégô òd ji pòpùlacji - ptôchë z pôłniô mieszkają ù se całi rok, a te żëjącé w ùmiarkòwónym klimace migrëją òd zélnikã do séwnikã nad [[Strzódzemné Mòrze]], abë wrócëc do swòjich gniôzd kòl strëmiannika. W przërównanim do [[Czôrnô kania|czôrny kanie]], ji syg je ògraniczony. W Pòlsce je to nielëczny lãgòwi ptôch. Kanie są pòtikóné blós na zôpadze i nordowim wschòdze kraju ([[Wiôlgôpòlskô]], [[Pòmòrskô|Pòmòrzé]], nordowi part [[Dólny Szląsk|Dólnégò Szląska]] a téż [[Warmiô]] i [[Mazurë]])<ref>Ludwik Tomiałojć, Tadeusz Stawarczyk: Awifauna Polski. Rozmieszczenie, liczebność i zmiany. Wrocław: PTPP "pro Natura", 2003, s. 203. [[:pl:Specjalna:Książki/839196261X|ISBN 83-919626-1-X.]]</ref> W zaòstałëch regionach je rzôdkô, widiwónô je òb czas migracji. Niechtërne kanie w Centralny Eùropie mògą zëmòwac kòl rakòwiszczów lub jinëch môlów bòkadnëch w jôdã, le zachòwanié to je nieregùlarné i robią to leno pòjedinczé òsobniczi<ref>Marcin Karetta: Atlas ptaków. Pascal, 2010. I[[:pl:Specjalna:Książki/9788375136555|SBN 978-83-7513-655-5.]]</ref>. == Znanczi gatënkù == [[Òbrôzk:Schwanz-Milane.jpg|mały|Ògònë kaniów: pò prawi czôrnô kania, na lewą – lësatô]] [[Òbrôzk:Red Kite (Milvus milvus) skull at the Royal Veterinary College anatomy museum.JPG|mały|Czaszka ''Milvus Milvus'']] Ptôch charakterizëje sã wëlecałą sztaturą i bestrim òpiórzenim. Jegò cało mô 60–72 cm długòscë<ref>Orta, J., Christie, D.A. & Garcia, E.F.J.: [https://www.hbw.com/species/red-kite-milvus-milvus Red Kite (Milvus milvus)]. [w:] del Hoyo, J., Elliott, A., Sargatal, J., Christie, D.A. & de Juana, E. (eds.). Handbook of the Birds of the World Alive.</ref>, a rozcygłosc jegò lecëdłów wënôszô 175–195 cm. Samce wôżą òd 800 do 1200 gramów, timczasã samice są wiãkszé i ważą midzë 1000 a 1300 gramów. Pòza różnicą we wôdze, midzë płcama ptôcha ni ma wiãkszich różniców - òbie pëłce są szlachòwno ùbarwióné – dërdzawé cało z cemniészima plecama i biôłima akcentama na lëcadłach. Ògón czerwònobruny z wiérzchù i jasnoszari òd spòdu, dosc mòcno wcãti (niżóden eùropejsczi drapieżnik ni mô tak mòcnégò wcãcô jak kania). Temù téż ògón je nôbarżi rozpòznôwalną znanką tegò ptôcha w loce na wiôldżich wësokòscach. Głowa, szëja i place òbsadzeniô skrzidłów jasnopòpielaté ze sztrichòwanim. Wiérzch cała dërdzawòbruny, spódk cała jasniészi, sztrichòwóny. Na dólny starnie skrzidła to dô wiôldżi biôłi plachc, na wiérzchny starnie przeznórtny pasyk. Żôłti dzób z czôrnym kùńcã, żółté nodżi. Rzôdkò zdôrzają sã ptôchë z [[Leùcyzm|leùcysticzną]] fòrmą. Szlachùjącô za nią czôrnô kania je përznã mniészô, mô wiele cemniészé òpiórzenié i mni wcãti ògón. W przërównanim do [[Mësznik|mësznika]] je wiele wikszô i mô dłëższé skrzidła. Pò lãgòwim czasu kanie mògą noclegòwac w karnach dochôdającëch nawetka do sta sztëk, chòc nie je tak drësznô jak czôrnô kania. Lësatô kania czãsto lecy na prostëch abò letkò zdżãtëch skrzidłach, co w sztôłce przëbacziwô lëtrã M. W chùtczim i chwatczim loce mô prawie wiedno rozkòscérzony ògón, chtëren czasã sã przekrzéwiô. Kanie żëją kòl 25 lat, chòc w 2023 we [[Wiôlgô Britaniô|Wiôldżi Britanii]] òstała nalazłô kania, chtërna dożëła 29 lat.<ref>[https://www.bbc.co.uk/news/uk-england-oxfordshire-65646200 "One of the first red kites released into Thames Valley dies"]. BBC News. 20 May 2023. Retrieved 31 July 2023.</ref> == Biotop i jôda == [[Òbrôzk:Red Kite R1 4513.jpg|mały|Lësatô kania w loce, [[Wiôlgô Britaniô]]]] === Biotop === Widné, lëstowaté i mieszóné, stôré lasë roscącé kòl łąków i pól, czãstò w òkòlim dolëznów rzék, stawów, czë skalëstëch ùbrzegòw. Pòtikónô je na nizynnëch terenach, jak téż w górach. === Jôda === Kanie są generalisticznyma drapieżnikama i scërzożércama, przez co jich dieta je tak różnorodnô, jak jich strzodowiszcze. Pòżiwiają sã môłima [[Krãgówce|krãgówcama]], przede wszëtczim drobnyma [[Sysczi|syskama]], taczima jak: [[Toczk|toczczi]], czë [[Grëzôk|grëzôczi]], ale téż [[Wieszczórka|wieszczórczi]], [[Òwôd|òwadë]] i [[Wãdzëbôk|wãdzëbôczi]]. Zdarziwô jima sã téż jesc rëbë wëszmërgniãté na sztrąd. Ptôchë te baro sprôwnie zbiérają òb czas nisczégò lotu, dzãka czemù jachtëją na swòje òfiarë. Rozmieją nawetka wëcygnąc biérno płëwającé na wòdze rebë. [[Òbrôzk:Rote Milane mit Beute.jpg|lewo|mały|Pôra kaniów z òfiarą, [[Niemieckô]]]] Òne wòlą chwacëc żëwą zdobëcz, niż jesc scërza. Zdôrzô jim sã atakòwac jinszé rabùszné ptôchë (nawetka te wiãkszé òd nich) w célu kradzeżë jichny òfiarë, czim badérowie dolmaczą nalôżóné w ji wnãtrznoscach resztczi zwiérzãt, jaczich kanie ni mògłebë ùpòlowac. Swòjich òfiarów wëzérają z gałãzów drzéwiãt, chòc òwadë i môłé sysczi rozmieje schwacëc nawetka òb czas wòlnégò chòdzeniô. Pòza tim lësaté kanie czasã plundrëją gniôzda [[Krëkòwaté|krëkòwatëch]]. == Lãgòwi cząd == [[Òbrôzk:Horst des Rotmilans (Milvus milvus).JPG|mały|Gniôzdo kani]] Kanie rozmnôżają sã, czej staną sã dwa lata stôré, chòc zdôrzają sã przëtrôfczi roczniôków, jaczim ùdało sã młodzëc<ref>Evans, I. M.; Cordero, P. J.; Parkin, D. T. (1998). "Successful breeding at one year of age by Red Kites Milvus milvus in southern England". Ibis. 140 (1): 53–57</ref>. Są to mònogamiczné ptôchë, chtërne w stałëch pòpùlacjach òstają w ti sami pôrze przez zëmã, òsoblëwò jeżlë ptôchë òstają na tim samim teritorium. Wãdrowné kanie pò zëmie wrôcają do tegò samégò gniôzda, czim pòdtrzimùją wiérnosc midzë ptôchama. === Gniôzdo === [[Òbrôzk:Milmil njg 980613.jpg|lewo|mały|Młodé kanie w gniôzdze. [[Berlëno]], [[Niemieckô]].]] Nôczãscy bùdowóné je na lëstowatim drzewie, na rozwietwienim gałãzów abò twélów, na wësokòscë midzë 12 a 15 métrów. Kanie wic wëbiérają drzewa na kraju lasa. Bùdową gniôzda zajmùją sã òbie pëłce. Samc haléwô wietwie ò długòscë 30–50 cm, a samica je ukłôdô. Pòdstawą je grëbô gałąz, scelonô mitczima roscënama, ale téż różnyma smiecama, np. papiorã, kòdrama, gnôtama czë skórama. Òba ptôchë dodają wiãcy materiałów òb czas inkùbacji i chòwaniô młodëch. Kanie jedno gniôzdô ùżiwają przez wiele lat<ref name=":0">Cramp, Stanley; et al., eds. (1980). "Milvus milvus Red Kite". Handbook of the Birds of Europe the Middle East and North Africa: The Birds of the Western Palearctic. Vol. II: Hawks to Bustards. Oxford: Oxford University Press. pp. 36–44. <nowiki>ISBN 978-0-19-857505-4</nowiki>.</ref>. Òkazjonalnie zdôrzô sã kanióm wëkòrzëstac stôré gniôzda [[Mësznik|mëszników]]. Òb czas lãgù kanie są agresywné wedle rabùsznëch ptôchów, tak tej w òkòlim jich gniôzda nie młodzą sã jinszé [[grabcaté]]. === Jaja === [[Òbrôzk:Milvus milvus MHNT.ZOO.2010.11.80.15.jpg|mały|Jaje lësati kani]] Kanie niesą sã leno rôz w rokù, midzë łżëkwiatã a majã<ref name=":0" />. Òb czas jednégò gòdowégò cządu kania niese wic midzë jednym, a trzema jajama, chòc òdnotowóné bëłë przëtrôfczi, gdze w lãgù bëło jaż piãc jôj. Jaja są biôłé z zemisto-czerwionyma kropkama. Statisticznie jaje wôżi 63 gramë i mô wëmiarë 57 mm x 47 mm.<ref name=":0" /> Jeżlë ląg òstónie stracóny, samica zôs zniese jaja.<ref name=":0" /> === Wësadowanié i dorôstanié === Jaja wësadowóné są przez kòl 30 dni przez samicã. Samc w tim czasu przënosy ji jôdã, chòc czasã zastãpùje swòją partnerkã, czej òna lecy jachtowac. Pizglãta pierszé dwa tidzenié żëcô spãdzywają w gniôzdze razã z nënką, chtërna kôrmi je jôdą halóną przez samca. Pózni òba ptôchë lôtają pò jestkù dlô młodëch. Pò 14 dniach młodé same mùszą sã karmic przëniosłim do gniôzda jedzenim. Kòl 45 dnia żëcô pizglãta zaczinają sã rãplowac na gałãze kòl gniôzda, le rzôdkò pòdlatują przed 48–50 dniã żëcô, a czasã nawetka do wiekù 60–70 dniów. Pòdlatawce spãdzywają jesz dwa do trzech tidzeniów w pòblëżim gniôzda z dokarmiającyma jich starszima. Pò dwùch latach młodé stają sã dozdrzeniałima ptôchama. == Galeriô == Òbrôzk:Leucistic Red Kite - Flickr - Andy Morffew.jpg|Leùcysticzny òsobnik Òbrôzk:Milhafre-real, Red Kite (52539996534).jpg|Lësatô kania w Pòrtugalii == Przëpisë == {{Przëpisë}} == Bùtnowé lënczi == {{Commons}} {{Kòntrola}} [[Kategòrëjô:Ptôchë]] [[Kategòrëjô:Jastrzibowaté]] [[Kategòrëjô:Ptôchë Eùropë]] [[Kategòrëjô:Rabùszné Ptôchë]] mnc5le8hwglza6hln19ne6zowtb0cst Szablóna:Miono 10 10527 204441 173972 2026-07-05T12:00:06Z Terrus38 14026 204441 wikitext text/x-wiki <br /><div style="text-align: center; width: 60%; margin: auto; padding: 1em; border: {{{border|solid 2px black}}}; background-color: {{{bgcolor|gold}}}; color: {{{fgcolor|black}}}; font-weight: bold;">[[File:Coat_of_arms_of_Kaszubians.png|50px|left]] Jeżlë wiész, że na môlëzna mô téż swòje [[Wiki:Normalizacëjô kaszëbsczi terminologiji|znormalizowóné kaszëbsczé miono]], òstawi òdkôz na starnie diskùsje. Nie zabôcz pòdac zdrzódło ti jinfòrmacje!</span></div><br /> slv7f3473hlrdndhvip341wgtrkf6ec Charnowò 0 10835 204513 198573 2026-07-06T11:58:05Z Iketsi 3254 [[Pòlsczi jãzëk|pòl.]]→{{jãz.|pl}}; ' – to je '→' – ' 204513 wikitext text/x-wiki [[Òbrôzk:Kosciol6- Charnowo.jpg|mały|Kòscół w Charnowie]] '''Charnowò''' ({{jãz.|pl}} ''Charnowo'') – [[wies]] w [[Gmina Ùszcz|Gminie Ùszcz]], w [[Stôłpsczi kréz|stołpsczim krézu]] [[pòmòrsczé wòjewództwò|pòmòrsczégò wòjewództwa]]. Tu je kòscół, a dzece w szkòle ùczą sã [[Kaszëbsczi jãzëk|kaszëbsczégò jãzëka]]. == Bùtnowé lënczi == {{Commons}} * [http://dir.icm.edu.pl/pl/Slownik_geograficzny/Tom_I/548 ''Charnowo'' w „Geògraficznym słowôrzu Pòlsczégò Królestwa i jinëch słowiańsczich krajów”] * [https://web.archive.org/web/20160116163609/http://spcharnowo.edu.pl/ szkòła] {{Ùzémk artikla}} {{Kòntrola}} [[Kategòrëjô:Gmina Ùszcz]] pymqszyngdr4a455r7xkkyav9v5bjc1 Milvus 0 11187 204482 203358 2026-07-05T19:34:56Z Rabùszniczka 19710 204482 wikitext text/x-wiki [[Òbrôzk:Red Kite and Black Kite NAUMANN.jpg|mały]] [[Òbrôzk:Milvus migrans front(ThKraft).jpg|mały|Czôrnô kania w loce. ]] '''Milvus'''<ref>[[Milvus]], [w:] Integrated Taxonomic Information System (ang.).</ref>, '''kania''' – rodzôj drapieżnëch ptôchów z pòdrodzëznë jastrzibów (''[[Accipitrinae]]'') w rodzëznie jastrzibòwatëch (''[[Accipitridae]]).'' == Wëstãpòwanié == [[Òbrôzk:Milvus milvus, Meyerode, Belgium 1.jpg|mały|Lësaté kanie w Belgii]] Gatënczi òbejmòwóné przez rodzôj żëją Eùropie, Azji, Africe i Òceanii <ref>D.W. Winkler, S.M. Billerman & I.J. Lovette: Hawks, Eagles, and Kites (Accipitridae), version 1.0.. W: S.M. Billerman, B.K. Keeney, P.G. Rodewald & T.S. Schulenberg (red.): [https://birdsoftheworld.org/bow/home Birds of the World]. Ithaca, NY: Cornell Lab of Ornithology, 2020. DOI: [https://birdsoftheworld.org/bow/species/accipi1/1.0/introduction 10.2173/bow.accipi1.01.] [dostęp 2020-06-05]. (ang.).</ref>. == Systematika == Rodzôj òbejmùje trze gatënczi: * ''Milvus milvus'' ([[Karol Linneùsz|Linnaeus]], 1758) – [[Lësatô kania]] * ''Milvus migrans'' ([[Boddaert]], 1783) – [[Czôrnô kania]] * ''Milvus aegyptius ([[Gmelin]], JF, 1788) – [[Egipskô kania]]'' == Òbaczë téż == {{#categorytree:Jastrzibowaté|hideroot=on|mode=pages}} == Przëpisë == {{Przëpisë}} == Lëteratura == * [[Bernard Zëchta]]: Słownik gwar kaszubskich na tle kultury ludowej, Ossolineum, Wrocław - Warszawa - Kraków 1968, tom II, s. 296. == Bùtnowé lënczi == {{Commons}} {{Ùzémk artikla}} {{Kòntrola}} [[Kategòrëjô:Jastrzibowaté]] [[Kategòrëjô:Kanie]] 6fp42z82s5zioqwx9u42a0dqj3svyb3 204485 204482 2026-07-05T19:56:31Z Rabùszniczka 19710 Etymòlogiô 204485 wikitext text/x-wiki [[Òbrôzk:Red Kite and Black Kite NAUMANN.jpg|mały|Czôrnô i lësatô kania.]] [[Òbrôzk:Milvus migrans front(ThKraft).jpg|mały|Czôrnô kania w loce. ]] '''Milvus'''<ref>[[Milvus]], [w:] Integrated Taxonomic Information System (ang.).</ref>, '''kania''' – rodzôj drapieżnëch ptôchów z pòdrodzëznë jastrzibów (''[[Accipitrinae]]'') w rodzëznie jastrzibòwatëch (''[[Accipitridae]]).'' == Wëstãpòwanié == [[Òbrôzk:Milvus milvus, Meyerode, Belgium 1.jpg|mały|Lësaté kanie w Belgii]] Gatënczi òbejmòwóné przez rodzôj żëją Eùropie, Azji, Africe i Òceanii <ref>D.W. Winkler, S.M. Billerman & I.J. Lovette: Hawks, Eagles, and Kites (Accipitridae), version 1.0.. W: S.M. Billerman, B.K. Keeney, P.G. Rodewald & T.S. Schulenberg (red.): [https://birdsoftheworld.org/bow/home Birds of the World]. Ithaca, NY: Cornell Lab of Ornithology, 2020. DOI: [https://birdsoftheworld.org/bow/species/accipi1/1.0/introduction 10.2173/bow.accipi1.01.] [dostęp 2020-06-05]. (ang.).</ref>. == Systematika == Rodzôj òbejmùje trze gatënczi: * ''Milvus milvus'' ([[Karol Linneùsz|Linnaeus]], 1758) – [[Lësatô kania]] * ''Milvus migrans'' ([[Boddaert]], 1783) – [[Czôrnô kania]] * ''Milvus aegyptius ([[Gmelin]], JF, 1788) – [[Egipskô kania]]'' === Etymòlogiô pòzwë === * ''Milvus'': {{łac.|milvus}} „kania”. * ''Ictinus'': gr. ικτιν ''iktin'' lub ικτινος ''iktinos'' „kania”. Tipòwi gatënk: ''Falco milvus'' {{small|Linnaeus, 1758}}. * ''Hydroictinia'': gr. ὑδρο- ''hudro-'' „wòdny-”, od ὑδωρ ''hudōr'', ὑδατος ''hudatos'' „woda”; rodzaj ''Ictinia'' {{small|Vieillot, 1816}} (kanialuk). Tipòwi gatënk: ''Falco migrans'' {{small|Boddaert, 1783}} == Òbaczë téż == {{#categorytree:Jastrzibowaté|hideroot=on|mode=pages}} == Przëpisë == {{Przëpisë}} == Lëteratura == * [[Bernard Zëchta]]: Słownik gwar kaszubskich na tle kultury ludowej, Ossolineum, Wrocław - Warszawa - Kraków 1968, tom II, s. 296. == Bùtnowé lënczi == {{Commons}} {{Ùzémk artikla}} {{Kòntrola}} [[Kategòrëjô:Jastrzibowaté]] [[Kategòrëjô:Kanie]] 44zv4dxmghla8grlphjjplkoxebri56 204487 204485 2026-07-05T20:25:50Z Terrus38 14026 drobné 204487 wikitext text/x-wiki [[Òbrôzk:Red Kite and Black Kite NAUMANN.jpg|mały|Czôrnô i lësatô kania.]] [[Òbrôzk:Milvus migrans front(ThKraft).jpg|mały|Czôrnô kania w loce. ]] '''Milvus'''<ref>[[Milvus]], [w:] Integrated Taxonomic Information System (ang.).</ref>, '''kania''' – rodzôj drapieżnëch ptôchów z pòdrodzëznë jastrzëbów (''[[Accipitrinae]]'') w rodzëznie jastrzëbòwatëch (''[[Accipitridae]]).'' == Wëstãpòwanié == [[Òbrôzk:Milvus milvus, Meyerode, Belgium 1.jpg|mały|Lësaté kanie w Belgii]] Gatënczi òbejmòwóné przez rodzôj żëją w Eùropie, Azji, Africe i Òceanii <ref>D.W. Winkler, S.M. Billerman & I.J. Lovette: Hawks, Eagles, and Kites (Accipitridae), version 1.0.. W: S.M. Billerman, B.K. Keeney, P.G. Rodewald & T.S. Schulenberg (red.): [https://birdsoftheworld.org/bow/home Birds of the World]. Ithaca, NY: Cornell Lab of Ornithology, 2020. DOI: [https://birdsoftheworld.org/bow/species/accipi1/1.0/introduction 10.2173/bow.accipi1.01.] [dostęp 2020-06-05]. (ang.).</ref>. == Systematika == Rodzôj òbejmùje trzë gatënczi: * ''Milvus milvus'' ([[Karol Linneùsz|Linnaeus]], 1758) – [[Lësatô kania]] * ''Milvus migrans'' ([[Boddaert]], 1783) – [[Czôrnô kania]] * ''Milvus aegyptius ([[Gmelin]], JF, 1788) – [[Egipskô kania]]'' === Etymòlogiô pòzwë === * ''Milvus'': [[Łacyńsczi jãzëk|łac.]] „kania”. * ''Ictinus'': gr. ικτιν ''iktin'' lub ικτινος ''iktinos'' „kania”. Tipòwi gatënk: ''Falco milvus'' {{small|Linnaeus, 1758}}. * ''Hydroictinia'': gr. ὑδρο- ''hudro-'' „wòdny-”, od ὑδωρ ''hudōr'', ὑδατος ''hudatos'' „woda”; rodzaj ''Ictinia'' {{small|Vieillot, 1816}} (kanialuk). Tipòwi gatënk: ''Falco migrans'' {{small|Boddaert, 1783}} == Òbaczë téż == {{#categorytree:Jastrzibowaté|hideroot=on|mode=pages}} == Przëpisë == {{Przëpisë}} == Lëteratura == * [[Bernard Zëchta]]: Słownik gwar kaszubskich na tle kultury ludowej, Ossolineum, Wrocław - Warszawa - Kraków 1968, tom II, s. 296. == Bùtnowé lënczi == {{Commons}} {{Ùzémk artikla}} {{Kòntrola}} [[Kategòrëjô:Jastrzibowaté]] [[Kategòrëjô:Kanie]] 1j50myosat6u6jtz25wttl2jquguny3 204502 204487 2026-07-06T11:22:05Z Iketsi 3254 [[Łacyńsczi jãzëk|łac.]]→{{jãz.|la}} 204502 wikitext text/x-wiki [[Òbrôzk:Red Kite and Black Kite NAUMANN.jpg|mały|Czôrnô i lësatô kania.]] [[Òbrôzk:Milvus migrans front(ThKraft).jpg|mały|Czôrnô kania w loce. ]] '''Milvus'''<ref>[[Milvus]], [w:] Integrated Taxonomic Information System (ang.).</ref>, '''kania''' – rodzôj drapieżnëch ptôchów z pòdrodzëznë jastrzëbów (''[[Accipitrinae]]'') w rodzëznie jastrzëbòwatëch (''[[Accipitridae]]).'' == Wëstãpòwanié == [[Òbrôzk:Milvus milvus, Meyerode, Belgium 1.jpg|mały|Lësaté kanie w Belgii]] Gatënczi òbejmòwóné przez rodzôj żëją w Eùropie, Azji, Africe i Òceanii <ref>D.W. Winkler, S.M. Billerman & I.J. Lovette: Hawks, Eagles, and Kites (Accipitridae), version 1.0.. W: S.M. Billerman, B.K. Keeney, P.G. Rodewald & T.S. Schulenberg (red.): [https://birdsoftheworld.org/bow/home Birds of the World]. Ithaca, NY: Cornell Lab of Ornithology, 2020. DOI: [https://birdsoftheworld.org/bow/species/accipi1/1.0/introduction 10.2173/bow.accipi1.01.] [dostęp 2020-06-05]. (ang.).</ref>. == Systematika == Rodzôj òbejmùje trzë gatënczi: * ''Milvus milvus'' ([[Karol Linneùsz|Linnaeus]], 1758) – [[Lësatô kania]] * ''Milvus migrans'' ([[Boddaert]], 1783) – [[Czôrnô kania]] * ''Milvus aegyptius ([[Gmelin]], JF, 1788) – [[Egipskô kania]]'' === Etymòlogiô pòzwë === * ''Milvus'': {{jãz.|la}} „kania”. * ''Ictinus'': {{jãz.|gr}} ικτιν ''iktin'' lub ικτινος ''iktinos'' „kania”. Tipòwi gatënk: ''Falco milvus'' {{small|Linnaeus, 1758}}. * ''Hydroictinia'': {{jãz.|gr}} ὑδρο- ''hudro-'' „wòdny-”, od ὑδωρ ''hudōr'', ὑδατος ''hudatos'' „woda”; rodzaj ''Ictinia'' {{small|Vieillot, 1816}} (kanialuk). Tipòwi gatënk: ''Falco migrans'' {{small|Boddaert, 1783}} == Òbaczë téż == {{#categorytree:Jastrzibowaté|hideroot=on|mode=pages}} == Przëpisë == {{Przëpisë}} == Lëteratura == * [[Bernard Zëchta]]: Słownik gwar kaszubskich na tle kultury ludowej, Ossolineum, Wrocław - Warszawa - Kraków 1968, tom II, s. 296. == Bùtnowé lënczi == {{Commons}} {{Ùzémk artikla}} {{Kòntrola}} [[Kategòrëjô:Jastrzibowaté]] [[Kategòrëjô:Kanie]] 2h9p1zveaev0kxcsnzov7bcu4aiiv0r Wenezuelskô 0 11696 204457 201932 2026-07-05T14:49:15Z DawnyTest 14843 204457 wikitext text/x-wiki {{Infobox-państwò |gwôsné miono = República Bolivariana de Venezuela |miono = Boliwiarańskô Repùblika Wenezueli |miono-genitiw = Wenezueli |fana = Flag of Venezuela.svg |herb = Coat of arms of Venezuela.svg |na karce = LocationVenezuela.svg |mòtto = |jãzëk = [[Szpańsczi jãzëk|Szpańsczi]] |stolëca = Caracas |fòrma państwa = repùblika |wiéchrzëzna =912,050 |procent-wòdë = |lëdztwò =31,250,306 | rok = 2024 |dëtk = Wenezuelsczi bolivar | kòd dëtka = VES |czasowô cona = |swiãto =5 lëpińca |himn = Gloria al Bravo Pueblo<br>(Chwała bëlnëmu lëdowi) <center>[[File:United States Navy Band - Gloria al Bravo Pueblo.ogg]]</center> |kòd = VEN, VE |Internet = .ve |telefón = +58|Prezydeńt=Delcy Rodríguez (p.ò.)|Premiéra=|Mònarcha= |aùtowi kòd=YV}} '''Wenezuelskô''' abò '''Wenezuelô''' (szp. ''Venezuela''), '''Boliwiarańskô Repùblika Wenezueli''' (szp: ''República Bolivariana de Venezuela'') je państwã w Pôłniowi Americe. Greńczë z [[Kolumbiô|Kolumbią]] (długosc grańcy 2341 km), [[Brazylskô|Brazylską]] (2137 km) i [[Gujana|Gujaną]] (789 km). Stolëca i nôwikszi gard to [[Caracas]]. W Caracas ùrodzył sã [[Simón Boliwar|Simón Bolívar]], ''El Libertador'', stolemni jenerôł i pôłniowò-amerikańsczi bòhater, od chtërnégò mô miono wenezuelsczi dëtk, bolivar. Nôrodné swiãto je 5 lëpińca, je to dzéń samòstójnoty<ref>[https://www.state.gov/releases/2025/07/message-to-the-venezuelan-people-on-independence-day Message to the Venezuelan People on Independence Day] [2025-08-19]</ref>. W Wenezueli żëją [[kapibara|kapibarë]]. Wiéchrzëzna to 912,050 km², lëdztwò: 31,250,306 w 2024<ref>[https://web.archive.org/web/20210531110017/https://www.cia.gov/the-world-factbook/countries/venezuela/ Venezuela] CIA World Factbook [2025-08-19]</ref>. <gallery mode="packed" caption="Galeriô Wenezueli"> Simón Bolívar by Ricardo Acevedo Bernal.jpg|Simón Bolívar PicoBolivar2.jpg|Pico Bolivar 2952-Danzas_Guanaguanare_de_Venezuela_no_Festival_folclorico_da_Coruña._(8200095256).jpg|Tuńc Guanaguanare </gallery> <gallery mode="packed"> Salto_del_Angel-Canaima-Venezuela03.JPG 2007_02_Capybaras_08.jpg|Kapibarë </gallery> <gallery mode="packed"> Tricolor.jpg|Sejm A_las_puertas_del_cielo.JPG|Puertas del cielo </gallery> == Przëpisë == {{Przëpisë}} == Bùtnowé lënczi == {{Commons}} {{Amerika}} {{Ùzémk artikla}} {{Kòntrola}} [[Kategòrëjô:Pôłniowô Amerika]] es10pikx5ss4xogo93wwlht6n4krmsw 204466 204457 2026-07-05T15:34:32Z Orbitminis 18250 pisënk 204466 wikitext text/x-wiki {{Infobox-państwò |gwôsné miono = República Bolivariana de Venezuela |miono = Boliwiarańskô Repùblika Wenezueli |miono-genitiw = Wenezueli |fana = Flag of Venezuela.svg |herb = Coat of arms of Venezuela.svg |na karce = LocationVenezuela.svg |mòtto = |jãzëk = [[Szpańsczi jãzëk|Szpańsczi]] |stolëca = Caracas |fòrma państwa = repùblika |wiéchrzëzna =912,050 |procent-wòdë = |lëdztwò =31,250,306 | rok = 2024 |dëtk = Wenezuelsczi bolivar | kòd dëtka = VES |czasowô cona = |swiãto =5 lëpińca |himn = Gloria al Bravo Pueblo<br>(Chwała bëlnëmu lëdowi) <center>[[File:United States Navy Band - Gloria al Bravo Pueblo.ogg]]</center> |kòd = VEN, VE |Internet = .ve |telefón = +58|Prezydeńt=Delcy Rodríguez (p.ò.)|Premiéra=|Mònarcha= |aùtowi kòd=YV}} '''Wenezuelskô''' abò '''Wenezuelô''' (szp. ''Venezuela''), '''Boliwiarańskô Repùblika Wenezueli''' (szp: ''República Bolivariana de Venezuela'') je państwã w [[Pôłniowô Amerika|Pôłniowi Americe]]. Grańczë z [[Kolumbiô|Kolumbią]] (dłużawa grańcë 2341 km), [[Brazylskô|Brazylską]] (2137 km) i [[Gujana|Gujaną]] (789 km). Stolëca i nôwikszi gard to [[Caracas]]. W Caracas ùrodzył sã [[Simón Boliwar|Simón Bolívar]], ''El Libertador'', stolëmny jenerôł i pôłniowò-amerikańsczi heroj, òd chtërnégò mô miono wenezuelsczi dëtk, bolivar. Nôrodné swiãto je 5 lëpińca, je to dzéń samòstójnotë<ref>[https://www.state.gov/releases/2025/07/message-to-the-venezuelan-people-on-independence-day Message to the Venezuelan People on Independence Day] [2025-08-19]</ref>. W Wenezueli żëją [[kapibara|kapibarë]]. Wiéchrzëzna to 912,050 km², lëdztwò: 31,250,306 w 2024<ref>[https://web.archive.org/web/20210531110017/https://www.cia.gov/the-world-factbook/countries/venezuela/ Venezuela] CIA World Factbook [2025-08-19]</ref>. <gallery mode="packed" caption="Galeriô Wenezueli"> Simón Bolívar by Ricardo Acevedo Bernal.jpg|Simón Bolívar PicoBolivar2.jpg|Pico Bolivar 2952-Danzas_Guanaguanare_de_Venezuela_no_Festival_folclorico_da_Coruña._(8200095256).jpg|Tuńc Guanaguanare </gallery> <gallery mode="packed"> Salto_del_Angel-Canaima-Venezuela03.JPG 2007_02_Capybaras_08.jpg|Kapibarë </gallery> <gallery mode="packed"> Tricolor.jpg|Sejm A_las_puertas_del_cielo.JPG|Puertas del cielo </gallery> == Przëpisë == {{Przëpisë}} == Bùtnowé lënczi == {{Commons}} {{Amerika}} {{Ùzémk artikla}} {{Kòntrola}} [[Kategòrëjô:Pôłniowô Amerika]] tky57ovwfcne03qpdnc6o9fol1p7mj1 Kapibara 0 11697 204472 202928 2026-07-05T16:23:42Z Orbitminis 18250 Dofùlowanié òd pòlsczi Wiczipedie, przëpisë, pisënk, encyklopedicznosc, galeriô, red. 204472 wikitext text/x-wiki [[Òbrôzk:Capivara(Hydrochoerus_hydrochaeris).jpg|mały|Kapibara (''Hydrochoerus hydrochaeris''), òna z dzecma]] '''Kapibara''' ([[Guarańsczi jãzëk|guarani]]: ''Kapiÿva'', [[Łacyńsczi jãzëk|łac]]. ''Hydrochoerus hydrochaeris'') – zort [[Sysczi|sysków]] z [[Rodzëzna (biologiô)|pòdrodzëznë]] [[Kapibôrë|kapibôr]] (Hydrochoerinae) w òbrãbie rodzëznë kawiowôtëch (Caviidae). Nôwikszi grëzôk z rodzëznë kawiowôtëch, ''Caviidae'', mòże wôżëc do 65 kg. Òna żëje w [[Pôłniowô_Amerika|Pôłniowi Americe]] i żëwi sã roscënama. == Geògraficzné rozplacowanié == Zort òbjimô gatënczi wëstãpùjącé w [[Strzédnô Amerika|Strzédnim]] ([[Panama]]) i [[Pôłniowô Amerika|Pôłniowim Americe]] ([[Kolumbiô]], [[Wenezuelskô|Wenezuelô]], [[Gujana]], [[Brazylskô|Brazyliô]], [[Ekwador]], [[Perú]], [[Bòliwiô]], [[Paragwaj]], [[Argentina]] i [[Urugwaj]])<ref>C.J. Burgin, D.E. Wilson, R.A. Mittermeier, A.B. Rylands, T.E. Lacher & W. Sechrest: [https://lynxnaturebooks.com/product/illustrated-checklist-of-the-mammals-of-the-world/ ''Illustrated Checklist of the Mammals of the World''] (kasz. ''Zòbrôzkòwónô lësta sysków swiata''). 1. part: Monotremata to Rodentia. Barcelona: Lynx Edicions, 2020, s. 552. [[:pl:Specjalna:Książki/9788416728343|ISBN 978-84-16728-34-3]]. (anielsczi).</ref><ref name=":0">T. Lacher, Jr: Family Caviidae (Cavies, Capybaras and Maras). W: D.E. Wilson, T.E. Lacher, Jr & R.A. Mittermeier: [[Handbook of the Mammals of the World|''Handbook of the Mammals of the World'']] ([[Kaszëbsczi jãzëk|kasz]]. ''Ùczbòwnik sysków swiata''). 6. part: Lagomorphs and Rodents I. Barcelona: Lynx Edicions, 2016, s. 436–437. [[:pl:Specjalna:Książki/9788494189234|ISBN 978-84-941892-3-4]]. ([[Anielsczi jãzëk|anielsczi]]).</ref><ref>D.E. Wilson & D.M. Reeder (red.): [https://www.departments.bucknell.edu/biology/resources/msw3/browse.asp?s=y&id=13400217 ''Hydrochoeris'']. [w:] ''Mammal Species of the World. A Taxonomic and Geographic Reference'' (Wëd. 3) [on-line]. Johns Hopkins University Press, 2005. [przëstãp 2026-07-05].</ref>. == Mòrfòlogiô == Długòta cała (bez ògòna) 1025–1340 mm, długòta ògòna 10–20 mm, długòta slédny stopë 200–250 mm, długòta ùcha 60–70 mm; masa cała 35–65 kg<ref name=":0" />. == Galeriô == <gallery mode="packed"> Òbrôzk:Capibara 1.jpg|mały|Kapibôrë jôdają trôwã Òbrôzk:2007_11_21_02_sCaplinSwimming.jpg|mały|Kapibara baro dobrze ùmieje płëwac </gallery> == Przëpisë == <references /> == Bùtnowé lënczi == {{Commons}}{{Kòntrola}} [[Kategòrëjô:Grëzôczi]] thba5w7ywmjp8j7n1183hsx2j4vkyax 204474 204472 2026-07-05T18:52:53Z Terrus38 14026 drobna kòrekta 204474 wikitext text/x-wiki [[Òbrôzk:Capivara(Hydrochoerus_hydrochaeris).jpg|mały|Kapibara (''Hydrochoerus hydrochaeris''), òna z dzecma]] '''Kapibara''' ([[Guarańsczi jãzëk|guarani]]: ''Kapiÿva'', [[Łacyńsczi jãzëk|łac]]. ''Hydrochoerus hydrochaeris'') – zort [[Cëcónë|sysków]] z [[Rodzëzna (biologiô)|pòdrodzëznë]] [[Kapibôrë|kapibôr]] (Hydrochoerinae) w òbrãbie rodzëznë kawiowatëch (Caviidae). Nôwikszi grëzôk z rodzëznë kawiowatëch, ''Caviidae'', mòże ważëc do 65 kg. Òna żëje w [[Pôłniowô_Amerika|Pôłniowi Americe]] i żëwi sã roscënama. == Geògraficzné rozplacowanié == Zort òbjimô gatënczi wëstãpùjącé we [[Westrzédnô Amerika|Westrzédny]] ([[Panama]]) i [[Pôłniowô Amerika|Pôłniowi Americe]] ([[Kolumbiô]], [[Wenezuelskô|Wenezuela]], [[Gujana|Gùjana]], [[Brazylskô|Brazyliô]], [[Ekwador]], [[Perú|Peru]], [[Bòliwiô]], [[Paragwaj]], [[Argentina]] i [[Ùrugwaj]])<ref>C.J. Burgin, D.E. Wilson, R.A. Mittermeier, A.B. Rylands, T.E. Lacher & W. Sechrest: [https://lynxnaturebooks.com/product/illustrated-checklist-of-the-mammals-of-the-world/ ''Illustrated Checklist of the Mammals of the World''] (kasz. ''Zòbrôzkòwónô lësta sysków swiata''). 1. part: Monotremata to Rodentia. Barcelona: Lynx Edicions, 2020, s. 552. [[:pl:Specjalna:Książki/9788416728343|ISBN 978-84-16728-34-3]]. (anielsczi).</ref><ref name=":0">T. Lacher, Jr: Family Caviidae (Cavies, Capybaras and Maras). W: D.E. Wilson, T.E. Lacher, Jr & R.A. Mittermeier: [[Handbook of the Mammals of the World|''Handbook of the Mammals of the World'']] ([[Kaszëbsczi jãzëk|kasz]]. ''Ùczbòwnik sysków swiata''). 6. part: Lagomorphs and Rodents I. Barcelona: Lynx Edicions, 2016, s. 436–437. [[:pl:Specjalna:Książki/9788494189234|ISBN 978-84-941892-3-4]]. ([[Anielsczi jãzëk|anielsczi]]).</ref><ref>D.E. Wilson & D.M. Reeder (red.): [https://www.departments.bucknell.edu/biology/resources/msw3/browse.asp?s=y&id=13400217 ''Hydrochoeris'']. [w:] ''Mammal Species of the World. A Taxonomic and Geographic Reference'' (Wëd. 3) [on-line]. Johns Hopkins University Press, 2005. [przëstãp 2026-07-05].</ref>. == Mòrfòlogiô == Dłużawa cała (bez ògòna) 1025–1340 mm, dłużawa ògòna 10–20 mm, dłużawa slédny stopë 200–250 mm, dłużawa ùcha 60–70 mm; masa cała 35–65 kg<ref name=":0" />. == Galeriô == <gallery mode="packed"> Òbrôzk:Capibara 1.jpg|Kapibarë jedzącé trôwã Òbrôzk:2007_11_21_02_sCaplinSwimming.jpg|Płiwającô kapibara </gallery> == Przëpisë == <references /> == Bùtnowé lënczi == {{Commons}}{{Kòntrola}} [[Kategòrëjô:Grëzôczi]] rzdngc2jc4ol3xbhil4745rhxbi92a5 Brëkòwnik:Orbitminis 2 12315 204468 204405 2026-07-05T15:42:43Z Orbitminis 18250 Anulowanié wersje [[Special:Diff/204405|204405]] aùtora [[Special:Contributions/Orbitminis|Orbitminis]] ([[User talk:Orbitminis|diskùsjô]]) teraz już wróciłem 204468 wikitext text/x-wiki {| class="wikitable" !<div align="center" class="usermessage">'''[[Òbrôzk:Kashubian board on shop, Hel, Poland.jpg|bezramki|205x205px]]''' ----[[Brëkòwnik:Orbitminis/Zapisownik|'''<big>ZAPISOWNIK</big>''']] | '''<big>Ò MIE</big>''' | '''<big>[[Diskùsëjô brëkòwnika:Orbitminis|DISKÙSJÔ BRËKÒWNIKA]]</big>''' </div>{{#babel:pl|en-2|de-1|csb-1}} == CSB [[Òbrôzk:Icons-flag-csb.png|bezramki]] == [[Òbrôzk:Kashubiaball.PNG|lewo|bezramki|94x94px]]Mójn! Nazéwóm sã Adóm. Jô nie ùmiã bëlno gadac pò kaszëbskù :( Ga robiã edicje w górnosorbsczi Wikipedii, pòstrzegł, że w kaszëbsczi Wiczipedie mô mni articzlów, za bëlno krótczich articzlów ë zacht jinszëch. Wiãc pòstanowił ju robic, ùlepszëc ë zbògacëc kaszëbską Wiczipedią. Òd 26 gromicznika 2026 r. jem dolmôczã interfejsu w kaszëbsczim jãzëkù w TranslateWiki. [https://translatewiki.net/wiki/User:Orbitminis] Nieraza pòprawiã edicje lëdzy z mniészą znajemnotë kaszëbsczégò jãzëka (jô téż móm, le piszã bezzmiłkòwò) bezzmiłkòwò pò kaszëbskù. W procëmnoscë zacht brëkòwników ùżëwóm téż słowôrze, cobë ùtrzëmac z lepszą kwalitetã pisënkù. I niestetë, tak samò jinszi brëkòwnicë, ni jem nôzmôrt aktiwnym wiczipedëstã w kaszëbsczi Wiczipedie z przëczënë paùz i jinëch zachów. Jem Pòlôchã, ni Kaszëbã i jeden z nômłodszëch wiczipedëstów na całczi kaszëbsczi Wiczipedie. <div style="font-family:Maiandra GD, sans-serif; font-size: 10pt; text-align: left;"><div style="border:1px solid #ccc; background: #fff; border-right:3px solid #ccc; border-bottom:3px solid #ccc; text-align: center; padding:3px; float:right; font-size: smaller; line-height: 1.3; margin-right: 4px;"><div style="width:100%">[[{{CURRENTDAYNAME}}|<font color="black">{{CURRENTDAYNAME}}</font>]]</div><div style="font-size: x-large; width: 100%;">[[{{CURRENTDAY}} {{CURRENTMONTHNAMEGEN}}|<font color="black">{{CURRENTDAY}}</font>]]</div><div style="width: 100%;">[[{{CURRENTMONTHNAME}}|<font color="black">{{CURRENTMONTHNAMEGEN}}</font>]] '''[[{{CURRENTYEAR}}|<font color="black">{{CURRENTYEAR}}</font>]] r.'''</div><div style="background: #eeeeee; color: #000;">'''{{CURRENTTIME}}''' UTC</div><div style="background: #eeeeee; color: #000;">+2</div></div></div> == PL [[Òbrôzk:Icons-flag-pl.png|bezramki|24x24px]] == Cześć! Nazywam się Adam. Ja nie umiem dobrze gadać po kaszubsku :( Gdy robię edycje w górnołużyckiej Wikipedii, zauważyłem, że w kaszubskiej Wikipedii ma mniej artykułów, za dużo krótkich artykułów i wiele innych. Więc postanowiłem już pracować, ulepszyć i wzbogacić kaszubską Wikipedię. Od 26 lutego 2026 r. jestem tłumaczem języka kaszubskiego w TranslateWiki. Nieraz poprawię edycje ludzi z mniejszą znajomością kaszubskiego (ja też mam, ale piszę poprawnie) poprawnie po kaszubsku. W przeciwieństwie wielu użytkowników korzystam także słowniki, żeby utrzymać z lepszą jakością pisowni. I niestety, tak jak inni użytkownicy, nie jestem najbardziej aktywnym wikipedystą w kaszubskiej Wikipedii z powodu przerw i innych rzeczy. Jestem Polakiem, nie Kaszubą i jeden z najmłodszych wikipedystów na całej kaszubskiej Wikipedii. ---- '''''<big>Kòżdi je geniuszã, le jeżlë òbsądzôsz rëbã pò ji mòcë wdrapëwónia sã na bómie, sprôwi nédżã żëcô wierzący, że je głëpô. – [[Albert Einstein]]</big>''''' ----[[Òbrôzk:HILLBLU lente.png|bezramki|26x26px]] UserScan''':''' <span class="plainlinks"> [https://xtools.wmflabs.org/pages/csb.wikipedia.org/Orbitminis ùsôdzoné] • [https://xtools.wmflabs.org/ec/?user=Orbitminis&project=csbwiki lëcznik] • [{{fullurl:Specjalnô:Rejestr|type=block&page=Brëkòwnik:{{urlencode:{{{1}}}}}}} blokadë] • [https://guc.toolforge.org/?user={{urlencode:{{{1|{{BASEPAGENAME}}}}}}}|globalny wkłôd] • [https://commons.wikimedia.org/w/index.php?title=Special%3ALog&type=upload&user={{urlencode:{{{1|{{BASEPAGENAME}}}}}}}&hide_thanks_log=1 wësłané lopczi]</span> ----'''<big>Artikle: 12 (csb) 100+ (swiat)</big>''' ----[[Zrzesznica Samòstójnëch Państw]], [[Wełtawa]], [[NATO]], [[Sedzëba]], [[Rzéka]], [[Bòsniacczi jãzëk]], [[II swiatowô wòjna]], [[Prezydeńt]], [[Kòsowò je serbsczé]], [[Friderik Chòpin]], [[United Artists]], [[Kaszëbskô Szwajcariô]] ----'''<big>Kategòrie: 3 (csb) 10+ (swiat)</big>''' ----[[:Kategòrëjô:Rzéczi|Rzéczi]], [[:Kategòrëjô:Historiô|Historiô]], [[:Kategòrëjô:Eùropejskô Ùniô|Eùropejskô Ùniô]] ----'''<big>Szablónë: 3 (csb) 10+ (swiat)</big>''' ----[[Szablóna:User ru-3]], [[Szablóna:Gduńsczi kréz]], [[Szablóna:NATO]] ----'''<big>Pòdzãkòwaniô: 23 (csb) 50+ (swiat)</big>''' ----[[Brëkòwnik:Iketsi|Iketsi]] (10+), [[Brëkòwnik:DawnyTest|DawnyTest]] (9), [[Brëkòwnik:Ясамойла|Ясамойла]] (1), [[Brëkòwnik:HaxelKaszëba|HaxelKaszëba]] (2) ----[[Òbrôzk:Traditional kashubian design example.png|bezramki|212x212px]] == Mòje brëkòkôstczë == {{Brëkòkôstka|[[Òbrôzk:Firefox logo, 2019.svg|35px]]|Hewòtny brëkòwnik kòrzëstô z [[Mozilla Firefox|Firefoxa]].|tło1=#2584A1|tło2=#F58E27}}{{Brëkòkôstka|{{NUMBEROFARTICLES}}|Hewòtny brëkòwnik mëslë, że w kaszëbsczi Wikipedie mómë za mało articzlów.|tło1=#7E7CF2|tło2=#4CEB3F}}{{Brëkòkôstka|[[Òbrôzk:Adminmop-no.gif|35px]]|Hewòtny brëkòwnik nie je sprôwnikã kaszëbsczi Wikipedie, le czejbë chcesz, zalecóm ti dòmòcë.}}{{Brëkòkôstka|[[Òbrôzk:No barnstars.png]]|Hewòtny brëkòwnik ni przejmujã gwiôzdek dlô mie.|tło1=#FF7670|tło2=#70A0FF}}{{Brëkòkôstka|[[Image:Herb Polski.svg|41px]]|Nen brëkòwnik je [[Pòlskô|Pòlôch]].|tło1=#942929|tło2=#942929|ramka=#000000}}[[Òbrôzk:Wikipedia-logo-transparent-cropped.png|lewo|bezramki]] |} nrlj35xfsvyu29qfasd4iu71in6i08o Brëkòwnik:Orbitminis/Zapisownik 2 12317 204470 204406 2026-07-05T16:19:46Z Orbitminis 18250 Anulowanié wersje [[Special:Diff/204406|204406]] aùtora [[Special:Contributions/Orbitminis|Orbitminis]] ([[User talk:Orbitminis|diskùsjô]]) 204470 wikitext text/x-wiki {| class="wikitable" !<div style="float: center; border: 1px solid #000; margin: 1px;"> {| cellspacing="0" style=" background: #EFFFFF" | style="font-size: 10pt; padding: 4pt; line-height: 1.25em; color: black; text-align: left;" | '''Witómë w mojim zapisowniku! Bôczënk: Hewòtny zapisownik służë sã do napisaniô nadchòdzącëch artiklów ë szablónów. Proszã ni editowac mojégò zapisownika! (ni jo)''' ----[[Òbrôzk:Sandbox.svg|lewo|bezramki|78x78px]]'''<big>ZAPISOWNIK [[Brëkòwnik:Orbitminis|Ò MIE]]</big>''' [[Òbrôzk:Debate, discussion.jpg|bezramki|195x195px]] '''<big>[[Diskùsëjô brëkòwnika:Orbitminis|DISKÙSËJÔ BRËKÒWNIKA]]</big>''' |}</div> === Niskùńczony kaszëbsczi dolmaczënk articzla Himn === [[Òbrôzk:Arvid_Liljelund_-_Man_Singing_Hymn_-_A_I_187_-_Finnish_National_Gallery.jpg|mały|''"Człowiek'' ''spiéwô himn"'' aùtorstwa Arvida Liljelunda (1884)]] '''Himn''' ({{Jãz.|gr}}. ὕμνος ''hýmnos'' – ''piesń pòchwalnô'') – ùroczëstô ë pòdniosłô piesń pòchwalnô, zwëczajno ò apòsztroficznym charakterze, kòmpònowanô na czesc bóżstwa, òsoblëwi òsobë, wëdarzeniô, tatczëznë (kraju), a negò téż ideji. == Historiô == W kòle eùropejsczi kùlturë himnë znóne były w basenie Morza Śródziemnego już w starożytności. Na stałe weszły do kanonów kulturowych wraz z rozwojem chrześcijaństwa, który uczynił swoim ten rodzaj literacki wzorując się na hebrajskim Starym Testamencie i judeochrześcijańskim Nowym Testamencie. === Wëjimk articzla [[Infòrmatika]] do skaszëbieniô === Nôwprzódné pòspòdle tegò, co stało sã infòrmatiką, bëłë rëchli jak wënalezënk współczesnégò cyfrowégò kòmpùtra. Maszinë do robieniô stałëch lëczbòwëch zadaniów, taczi jak lëczbòwnica, bëłë znóné ju w starożetnoscë i pòmôgałë w lëczenium, na przikłôd w mnojeniu ë dzeleniu. Algòritmë do robieniô òbliczeniów bëłë brëkòwóné ju w starożetnoscë, jesz przed rozwitiém zaawansowonégò kòmpùtrovégò sprzãtu. |} o6gwj1rulf78eo9v3c1g4116429xg39 204471 204470 2026-07-05T16:23:03Z Orbitminis 18250 do skùńczenia bò pòlskô Wiczipediô pò prôwdze je zadreżno zaawansowóny jak nie wiem co 204471 wikitext text/x-wiki {| class="wikitable" !<div style="float: center; border: 1px solid #000; margin: 1px;"> {| cellspacing="0" style=" background: #EFFFFF" | style="font-size: 10pt; padding: 4pt; line-height: 1.25em; color: black; text-align: left;" | '''Witómë w mojim zapisowniku! Bôczënk: Hewòtny zapisownik służë sã do napisaniô nadchòdzącëch artiklów ë szablónów. Proszã ni editowac mojégò zapisownika! (ni jo)''' ----[[Òbrôzk:Sandbox.svg|lewo|bezramki|78x78px]]'''<big>ZAPISOWNIK [[Brëkòwnik:Orbitminis|Ò MIE]]</big>''' [[Òbrôzk:Debate, discussion.jpg|bezramki|195x195px]] '''<big>[[Diskùsëjô brëkòwnika:Orbitminis|DISKÙSËJÔ BRËKÒWNIKA]]</big>''' |}</div> === Niskùńczony kaszëbsczi dolmaczënk articzla [[Kapibara]] === == Systematiczny pòdzôł == Do rodzaju należą następujące występujące współcześnie gatënczi<ref name="mdd" /><ref name="amw" /><ref name="Burgin&inni" /><ref name="Cichocki&inni" />: {| class="wikitable" !Céchùnk !Gatënk !Aùtór i rok òpisënkù !Zwëczajòwô pòzwa<ref name="Cichocki&inni" /> !Pòdgatënczi<ref name="Lacher" /><ref name="Burgin&inni" /><ref name="amw" /> !Geògraficzné rozplacowanié<ref name="Lacher" /><ref name="Burgin&inni" /><ref name="amw" /> !Spòdleczné wëmiarë<ref name="Lacher" /><ref name="amw" /><ref>'''DC''' – długość ciała; '''DO''' – długość ogona; '''MC''' – masa ciała</ref> !Sztatus [[Międzynarodowa Unia Ochrony Przyrody|IUCN]]<ref>{{cytuj stronę|url=https://www.iucnredlist.org/search?taxonomies=135300&searchType=species|tytuł=Taxonomy: ''Hydrochoerus'' – Genus|opublikowany=The IUCN Red List of Threatened Species|data dostępu=2025-01-06|język=en}}</ref> |- |[[Òbrôzk:Hydrochoeris_hydrochaeris_(capyvara)_at_the_Faunistic_Park_"La_Torbiera"_at_Agrate_Conturbia_village,_Piemonte_(Italy).jpg|centruj|150x150px]] |''Hydrochoerus hydrochaeris'' |([[Karol Linneùsz|Linnaeus]], 1766) |[[kapibara wielka]] |[[Takson monotypowy|gatunek monotypowy]] |na wschód od [[Andy|Andów]] w Kolumbii, Wenezueli, regionie [[Gujana (region)|Gujana]], Brazylii, Ekwadorze, Peru, Boliwii, Paragwaju, Urugwaju oraz północno-zachodniej i wschodniej Argentynie |DC: 107–134 cm DO: 1–2 cm MC: 35–65 kg | align="center" |{{Colorbox|#53bc36|{{Kolor|#ffffff|'''LC'''}}}} |- |[[Òbrôzk:Hydrochoerus_isthmius.jpg|centruj|150x150px]] |''Hydrochoerus isthmius'' |[[Edward Alphonso Goldman|E.A. Goldman]], 1912 |[[kapibara mała]] |gatunek monotypowy |wschodnia Panama, zachodnia Kolumbia i północno-zachodnia Wenezuela ([[Zulia]]) |DC: około 102 cm DO: brak danych MC: brak danych | align="center" |{{Colorbox|#c3c3c3|{{Kolor|#ffffff|'''DD'''}}}} |} Kategorie IUCN: {{Colorbox|#53bc36|{{Kolor|#ffffff|'''LC'''}}}} – [[gatunek najmniejszej troski]], {{Colorbox|#c3c3c3|{{Kolor|#ffffff|'''DD'''}}}} – [[Czerwona księga gatunków zagrożonych|gatunki o nieokreślonym stopniu zagrożenia]]. Opisano również gatunki wymarłe z [[Pliocen|pliocenu]] i [[Plejstocen|plejstocenu]]<ref name="zij" />: * ''[[Hydrochoerus ballesterensis]]'' {{small|([[Carlos Rusconi|Rusconi]], 1934)}}<ref name="Rusconi" /> (Argentyna) * ''[[Hydrochoerus gaylordi]]'' {{small|([[Ross Douglas Earle MacPhee|MacPhee]], [[Ronald Singer|Singer]] & [[Michael Diamond (paleontolog)|Diamond]], 2000)}}<ref name="mac&inni" /> ([[Grenada]]). * ''[[Hydrochoerus hesperotiganites]]'' {{small|[[Richard White|R. White]], [[Jim Mead|Mead]], [[Gary Morgan|Morgan]] & [[Thomas A. Deméré|Deméré]], 2022}}<ref name="whi&inni" /> ([[Stany Zjednoczone]]) === Wëjimk articzla [[Infòrmatika]] do skaszëbieniô === Nôwprzódné pòspòdle tegò, co stało sã infòrmatiką, bëłë rëchli jak wënalezënk współczesnégò cyfrowégò kòmpùtra. Maszinë do robieniô stałëch lëczbòwëch zadaniów, taczi jak lëczbòwnica, bëłë znóné ju w starożetnoscë i pòmôgałë w lëczenium, na przikłôd w mnojeniu ë dzeleniu. Algòritmë do robieniô òbliczeniów bëłë brëkòwóné ju w starożetnoscë, jesz przed rozwitiém zaawansowonégò kòmpùtrovégò sprzãtu. |} r4y70duofkmydvn7negt2pmf4cz1iyh Diskùsëjô brëkòwnika:Orbitminis 3 12323 204442 202197 2026-07-05T12:50:46Z Terrus38 14026 /* Òddzãkòwanié */ nowa sekcja 204442 wikitext text/x-wiki <div align="center" class="usermessage">Witôjtaż w mòji diskùsji brëkòwnika! Mòżesz téż pisac pò kaszëbskù, pò pòlskù, pò anielskù i pò miemiecckù, a pònemù bãdã òdpòwiadał. ----[[Òbrôzk:Sandbox.svg|lewo|bezramki|78x78px]][[Brëkòwnik:Orbitminis/Zapisownik|<big>ZAPISOWNIK</big>]] [[Brëkòwnik:Orbitminis|<big>Ò MIE</big>]] [[Òbrôzk:Debate,_discussion.jpg|bezramki|195x195px]] '''<big>DISKÙSËJÔ BRËKÒWNIKA</big>''' </div> <div align="center" class="disscusion"> == Poprawanie linków == Hej. Nie musisz wykonywać takich poprawek, jak <tt><nowiki>[[Gduńsk]]ù -> [[Gduńsk|Gduńskù]]</nowiki></tt> – w najbliższych dniach (może tygodniach) konfiguracja linków zostanie naprawiona na&nbsp;poziomie całej wiki. Pozdrawiam, [[Brëkòwnik:Tsca|tsca]] ([[Diskùsëjô brëkòwnika:Tsca|diskùsëjô]]) 21:01, 31 zél 2025 (CEST) :Wiem, proszę pana. Ale ja muszę poprawiać kaszubską Wikipedię, żeby nie były takich błędów ortograficznych jak "[[Gduńsk]]ù" i "Jop Polsczei jaczieego", bo te błędy mogą sprawiać kaszubską Wikipedię jako encyklopedię trzeciego świata jak najmniejsze Wikipedie w różnych językach. I oczywiście te źródła, artykuły, samouczki i szablony niektóre wyglądają staro jak w latach 2000-2010, więc muszą mieć nowe źródła, świeże infomacje i szablony podobne do polskiej Wikipedii po kaszubsku. Więc przepraszam najmocniej za takie, które spełniłem. Pozdrawiam, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 10:12, 1 Séw 2025 (CEST) == Automatycznie tłumaczone artykuły == Wklejanie automatycznie tłumaczonych artykułów mija się z celem. Skoro, jak sam deklarujesz na swojej stronie użytkownika, nie znasz języka kaszubskiego, nie jesteś w stanie zauważyć ani poprawić błędów w maszynowo przetłumaczonych treściach. Hasła utworzone w ten sposób niestety muszą zostać usunięte. [[Brëkòwnik:Tsca|tsca]] ([[Diskùsëjô brëkòwnika:Tsca|diskùsëjô]]) 23:28, 29 ruj 2025 (CET) :czemu? [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 16:11, 18 lës 2025 (CET) ::CSB: Mòjn panie Tsca! Dzysë jô sã dowiedzôł twòją nôùczkã, żëbë nie wlëmic "aùtomaticznie dolmaczonëch artiklów". Wiãc pòstanowił ùżëc słowôrza do stwórzbë artiklów, żëbë bëło trochã lepszé artiklów z pasëjącą pisënką. Żle w mojich artiklach nalézają sã felë, dôj mni znac. Pozdrôwióm, Orbitminis. ::PL: Cześć, panie Tsca! Dzisiaj ja się dowiedziałem twoją nauczkę, żeby nie wklejać „automatycznie tłumaczonych artykułów”. Więc postanowiłem użyć słownika do stworzenia artykułów, żeby było trochę lepsze artykułów z odpowiednią ortografią. Jeżeli w moich artykułów znajdują się błędy, daj mnie znać. Pozdrawiam, ::[[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 12:45, 9 gro 2026 (CET) == Odpowiedź == Cześć, po pierwsze, wybacz, że nie zareagowałem na [[Diskùsëjô brëkòwnika:DawnyTest#Szablónë pòtrzébùją zmianë ë aktualizacëji|twoją starą wiadomość]] o dyskusji na naszej wiki. W pełni się zgadzam, iż zmienione muszą zostać szablony, artykuły itp. Próbowałem podjąć się [[Szablóna:Infobox pòlitik|trudu ich tworzenia]] i edytowania (konkretnie infoboksów). Takowym był ''[[Szablóna:Infobox-państwò]]'', który w mojej opinii posiadał pewne problemy, które udało mi się zaleczyć. Posiłkowałem się m.in. polską wiki i tak na przykład mamy tam rewolucyjny wynalazek opcjonalnych parametrów. Po drugie, odnosząc się do twojej wiadomości, czy jest coś konkretnego co, twoim zdaniem, dobrze by było zmienić w szablonach lub ewentualnie by jakieś szablony utworzyć? Jestem chętny by podjąć się współpracy. Pozdrawiam cieplutko.<br> <small>A po polsku odpisałem, bo mi było szybciej, nie znam kaszubskiego na tyle, by bezbłędnie wytworzyć taką wiadomość w tak krótkim czasie.</small> [[Brëkòwnik:DawnyTest|DawnyTest]] ([[Diskùsëjô brëkòwnika:DawnyTest|diskùsëjô]]) 21:25, 18 str 2026 (CET) == Pòdzãkòwanié a pëtanié == Laudetur Iesus Christus et Semper Virgo Maria! Jô bëm chcôł Cë baro pòdzãkòwac za docenienié mòji robòtë z ùsôdzaniém wiadłokôstków dlô kaszëbsczi Wikipedëje a przëznanié mie pòczestny gwiôzdë. Hewò je dlô mie wiôlgą bùchą, eż jô mògã na taczé mòdło czënic nen môl jesz lepszim a snôższim. Takô deja je mie blizą òb robienié nowich szablonów a robòtã nad artiklama, co do jaczich wiedno jô móm starã, abë bëłë jak nôkwalitetniészé ë jak nôbarżi rozkòscérzóné. Przi ti lëżnoscë jô bëm chcôł Cë ò jedno zapëtôc. Jô trafił niedôwno na twòją starnã brëkòwnikã ë jô móm wiôldżé ùwôżanié do tewò, jak Të fejn to zrobił. Mògã jô ùżëc twòjéwò kòdù do swòjich starnów brëkòwnikã. Jô w nôblëższim czasu chcã zabrac sã za ji ùsôdzenié na pòlsczi a anielsczi Wikipedëjë, a téż jô nie òdsôdzôm ùprawieniégò ë przebùdérowaniéwò téż mòji starnë brëkòwnika z kaszëbsczi Wikipedëjë. Jô z górë dzãkùjã, pòzdrówiôm a żëczã bëlni, ùbëtni nocë. Òstani z Bògã! PS. Jakbyś czegoś nie rozumiał z tego, co wyżej napisałem, śmiało napisz, a ja to ci to napiszę po polsku. [[Brëkòwnik:RemusWej|RemusWej]] ([[Diskùsëjô brëkòwnika:RemusWej|diskùsëjô]]) 01:29, 20 łżë 2026 (CEST) :Mòjn ë ni mô sprawë, panie Jakùbie! Przédnã przëczënã, dlôcze przëznôł pòczestny gwiôzdë za stwòrzenié zacht szablónów i zbògacënié kaszëbsczi Wikipedie je, że w kaszëbsczi Wikipedie, tak jak wszelejaczé wersje môlowëch jãzëków nicht nie przëznôlë zwëkòwich abò wiãkszëch brëkòwników nôdgrodë, żebë mòtiwòwac i òstac na długò, a pózni przestôlë prôcowac dlô kaszëbsczi Wikipedie i òpùscëc to całowno. Dlôtegò pòstanowił przëznac dlô Cebie gwiôzdë, żëbë mòtiwòwac i òstac na długò. Òczëwisce ni mògã dużo gadac z tim, co napisôł. Bóg zapłac za stwòrzenié dlô mie diskùsje, bò z tim mie ceszy :) I żëczë bëlno zdrowiégò i ùcemiãgã za stwòrzenié taczé fejn cuda. (wëbaczë za pisënkòwé felë) Gòrąco pòzdrôwióm, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 12:05, 20 łżë 2026 (CEST) ::Jô téż pòzdrówiôm. Le co do mòjégò pëtaniégò niżi, wëdôlmaczã sprawã pò pòlskù, abë bëło lżi to bëlno zrozmiec. ::Zauważyłem, że fajnie zbudowałeś swoją stronę użytkownika na kaszubskiej Wikipedii. Bardzo mi podoba rozwiązanie, jakiego użyłeś, by to usystematyzować. Pytałem zaś o to, czy mógłbym w przyszłości użyć twojego kodu przy tworzeniu moich osobistych stron użytkownika? Myślę tu głównie o stronie na polską Wikipedię (cóż, jako że tam również coś czasem napiszę lub zedytuję, wypadałoby, abym w końcu ją stworzył XD),oraz anglojęzyczną, ale nie wykluczam też w przyszłości przebudowy tej już stworzonej przeze mnie na kaszubskiej Wikipedii. [[Brëkòwnik:RemusWej|RemusWej]] ([[Diskùsëjô brëkòwnika:RemusWej|diskùsëjô]]) 13:05, 21 łżë 2026 (CEST) == Pòdzãkòwanié za przeniosbã == Wiôldżé Bóg zapłac Cë za przeniesenié artikla ò Mòdżorów Pioterze na jegò docélowi môl. Jô jem baro rôd, eż mòja robòta je òbzéranô a docenianiô bez jinszich wikipedistów. Z drëdżi starnë jô baro przeprôszóm za felënk zdrzódłów. To, eż jô jesz jich ni miôł dodóné, bëło jedurnym pòwòdã, dlô jaczégò articzel béł jesz w zapisownikù. Wczorô miôł jem to wnetka zrobióné, le pò czilkù gòdzënach zmërkôł jem, eż jô nie zapisôł wińdzeniégò swòji robòtë, a jakno eż mòji laptop sã aùtomaticzno zrestartowôł, stracëł jem wszëtczé brzôdë mòji robòtë z ùzdrzódłowianiém. Terôzka jô mdã te zdrzódła dodawac òd zôczątkù a jô mdã miec starã, abë jesz dzys ònë bëłë dodóné. Jesz rôz dzãkã a żëczã jem milëczni majówczi. :) [[Brëkòwnik:RemusWej|RemusWej]] ([[Diskùsëjô brëkòwnika:RemusWej|diskùsëjô]]) 12:17, 1 môj 2026 (CEST) :Mòjn ë ni mô sprawë! Przédnã przëczënã, dlôcze przeniosł twój zapisownik-articzel ò Mòdżorów Pioterze na jegò docélowi môl, to że twój zapisownik wëzdrzi jak na dłudżi i òsoblëwi, pòniewôż w kaszëbsczi Wikipedie mómë za mało dłudżich articzlów, bò ni chcą pòswiãcëwac przënômni 30% czasu na editowanié i dofùlowiwanié kaszëbsczich rzeczy, dopòczi staną sã niaktualné i stôré. Dlôte pòstanowił to robic. Òczëwisce w dodôtkù dodôł wiki-fónkcje taczich jak Commons, kategòrie i zacht jinszëch i kòrektowôł je. Nic sã ni stało, że ni dodôł u Cebie zdrzódłów, bò nierôz mògą bëc problemë. Òczëwisce czejbë chcesz zdolmaczëc interfejs MediaWiki na kaszëbsczi (bò stwòrzëł w Karczmie diskùsje), klëkni tuwò: [https://translatewiki.net TranslateWiki]! Dzãka za stwòrzenié diskùsje ò mie :) Cebie téż żëczã jem milëczni majówczi i Òstani z Bògã! Pòzdrówióm, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 12:59, 1 môj 2026 (CEST) == Słów czile == Mòjn! Miło je widzec, że persónë taczé jak të, chcą sã angażowac i robic dlô kaszëbiznë na tak apartny métel jak rozwij Wikipedie – móm dlô ce bédënk, cobë më z @[[Brëkòwnik:Rzadtymczasowy|Rzadtymczasowy]] mòglë bëc z Tobą w łączbie, pòmagac (np. jinyma słowarzama niż niedoskònałi sloworz.org) i dzejac mët, môsz të Discorda abò cos jinszégò do kòmùnikacje? [[Brëkòwnik:Terrus38|Terrus38]] ([[Diskùsëjô brëkòwnika:Terrus38|diskùsëjô]]) 15:35, 15 cze 2026 (CEST) :Witam, panie Terrus! Na razie nie będę mówił po kaszubsku, bo słowniki kaszubskie w internecie są jeszcze ograniczone, ale na końcu wstawię pozdrowienie i podziękowanie do Ciebie. Dziękuję za poprawki gramatyczne moich tłumaczeń w TranslateWiki. I oczywiście odkąd zrobiłeś dzisiaj dyskusję, tak, ja zaangażowałem do dyskusji dotyczące gramatyki kaszubszczyzny, bo pan i twoi koledzy robicie małą debatę. Mówiąc zaangażowaniu, będę podawał kroki: :# Nie posiadam żadnych komunikatorów, bo komunikatory według mnie są nieprzydatne i niepotrzebne, e-mail też. :# Na razie wolę robić edycje w kaszubskiej Wikipedii i je rozbudować. :PS: Napisałem odrobinę profesjonalnie, bo tak muszę. Dzãkùjã baro za stwòrzenié diskùsje dlô mie i serdeczno pòzdrówióm, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 16:22, 15 cze 2026 (CEST) </div> == Òddzãkòwanié == Adjé, Orbitminis, dzãka za Twój wkłôd we Wikipediã i żëczã wszëtczégò bëlnégò! Jô ale prosto pòprôwiôł pisënk a sztilistikã pò pòprzédnëch edicjach, jô nie rëmôł bezpòtrzébno akapitów encyklopedicznégò tekstu, jaczi pisalë jiny, czë Të, czë jinszi. Jô nie jem Tobie za wroga, a za wespółrobòtnika. W kòżdim razu, pòzdrówk. [[Brëkòwnik:Terrus38|Terrus38]] ([[Diskùsëjô brëkòwnika:Terrus38|diskùsëjô]]) 14:50, 5 lëp 2026 (CEST) 47w3gceeaeidl106h5cu3ha6u9rgrbh 204445 204442 2026-07-05T13:51:18Z Orbitminis 18250 /* Òddzãkòwanié */ Odpowiedź 204445 wikitext text/x-wiki <div align="center" class="usermessage">Witôjtaż w mòji diskùsji brëkòwnika! Mòżesz téż pisac pò kaszëbskù, pò pòlskù, pò anielskù i pò miemiecckù, a pònemù bãdã òdpòwiadał. ----[[Òbrôzk:Sandbox.svg|lewo|bezramki|78x78px]][[Brëkòwnik:Orbitminis/Zapisownik|<big>ZAPISOWNIK</big>]] [[Brëkòwnik:Orbitminis|<big>Ò MIE</big>]] [[Òbrôzk:Debate,_discussion.jpg|bezramki|195x195px]] '''<big>DISKÙSËJÔ BRËKÒWNIKA</big>''' </div> <div align="center" class="disscusion"> == Poprawanie linków == Hej. Nie musisz wykonywać takich poprawek, jak <tt><nowiki>[[Gduńsk]]ù -> [[Gduńsk|Gduńskù]]</nowiki></tt> – w najbliższych dniach (może tygodniach) konfiguracja linków zostanie naprawiona na&nbsp;poziomie całej wiki. Pozdrawiam, [[Brëkòwnik:Tsca|tsca]] ([[Diskùsëjô brëkòwnika:Tsca|diskùsëjô]]) 21:01, 31 zél 2025 (CEST) :Wiem, proszę pana. Ale ja muszę poprawiać kaszubską Wikipedię, żeby nie były takich błędów ortograficznych jak "[[Gduńsk]]ù" i "Jop Polsczei jaczieego", bo te błędy mogą sprawiać kaszubską Wikipedię jako encyklopedię trzeciego świata jak najmniejsze Wikipedie w różnych językach. I oczywiście te źródła, artykuły, samouczki i szablony niektóre wyglądają staro jak w latach 2000-2010, więc muszą mieć nowe źródła, świeże infomacje i szablony podobne do polskiej Wikipedii po kaszubsku. Więc przepraszam najmocniej za takie, które spełniłem. Pozdrawiam, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 10:12, 1 Séw 2025 (CEST) == Automatycznie tłumaczone artykuły == Wklejanie automatycznie tłumaczonych artykułów mija się z celem. Skoro, jak sam deklarujesz na swojej stronie użytkownika, nie znasz języka kaszubskiego, nie jesteś w stanie zauważyć ani poprawić błędów w maszynowo przetłumaczonych treściach. Hasła utworzone w ten sposób niestety muszą zostać usunięte. [[Brëkòwnik:Tsca|tsca]] ([[Diskùsëjô brëkòwnika:Tsca|diskùsëjô]]) 23:28, 29 ruj 2025 (CET) :czemu? [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 16:11, 18 lës 2025 (CET) ::CSB: Mòjn panie Tsca! Dzysë jô sã dowiedzôł twòją nôùczkã, żëbë nie wlëmic "aùtomaticznie dolmaczonëch artiklów". Wiãc pòstanowił ùżëc słowôrza do stwórzbë artiklów, żëbë bëło trochã lepszé artiklów z pasëjącą pisënką. Żle w mojich artiklach nalézają sã felë, dôj mni znac. Pozdrôwióm, Orbitminis. ::PL: Cześć, panie Tsca! Dzisiaj ja się dowiedziałem twoją nauczkę, żeby nie wklejać „automatycznie tłumaczonych artykułów”. Więc postanowiłem użyć słownika do stworzenia artykułów, żeby było trochę lepsze artykułów z odpowiednią ortografią. Jeżeli w moich artykułów znajdują się błędy, daj mnie znać. Pozdrawiam, ::[[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 12:45, 9 gro 2026 (CET) == Odpowiedź == Cześć, po pierwsze, wybacz, że nie zareagowałem na [[Diskùsëjô brëkòwnika:DawnyTest#Szablónë pòtrzébùją zmianë ë aktualizacëji|twoją starą wiadomość]] o dyskusji na naszej wiki. W pełni się zgadzam, iż zmienione muszą zostać szablony, artykuły itp. Próbowałem podjąć się [[Szablóna:Infobox pòlitik|trudu ich tworzenia]] i edytowania (konkretnie infoboksów). Takowym był ''[[Szablóna:Infobox-państwò]]'', który w mojej opinii posiadał pewne problemy, które udało mi się zaleczyć. Posiłkowałem się m.in. polską wiki i tak na przykład mamy tam rewolucyjny wynalazek opcjonalnych parametrów. Po drugie, odnosząc się do twojej wiadomości, czy jest coś konkretnego co, twoim zdaniem, dobrze by było zmienić w szablonach lub ewentualnie by jakieś szablony utworzyć? Jestem chętny by podjąć się współpracy. Pozdrawiam cieplutko.<br> <small>A po polsku odpisałem, bo mi było szybciej, nie znam kaszubskiego na tyle, by bezbłędnie wytworzyć taką wiadomość w tak krótkim czasie.</small> [[Brëkòwnik:DawnyTest|DawnyTest]] ([[Diskùsëjô brëkòwnika:DawnyTest|diskùsëjô]]) 21:25, 18 str 2026 (CET) == Pòdzãkòwanié a pëtanié == Laudetur Iesus Christus et Semper Virgo Maria! Jô bëm chcôł Cë baro pòdzãkòwac za docenienié mòji robòtë z ùsôdzaniém wiadłokôstków dlô kaszëbsczi Wikipedëje a przëznanié mie pòczestny gwiôzdë. Hewò je dlô mie wiôlgą bùchą, eż jô mògã na taczé mòdło czënic nen môl jesz lepszim a snôższim. Takô deja je mie blizą òb robienié nowich szablonów a robòtã nad artiklama, co do jaczich wiedno jô móm starã, abë bëłë jak nôkwalitetniészé ë jak nôbarżi rozkòscérzóné. Przi ti lëżnoscë jô bëm chcôł Cë ò jedno zapëtôc. Jô trafił niedôwno na twòją starnã brëkòwnikã ë jô móm wiôldżé ùwôżanié do tewò, jak Të fejn to zrobił. Mògã jô ùżëc twòjéwò kòdù do swòjich starnów brëkòwnikã. Jô w nôblëższim czasu chcã zabrac sã za ji ùsôdzenié na pòlsczi a anielsczi Wikipedëjë, a téż jô nie òdsôdzôm ùprawieniégò ë przebùdérowaniéwò téż mòji starnë brëkòwnika z kaszëbsczi Wikipedëjë. Jô z górë dzãkùjã, pòzdrówiôm a żëczã bëlni, ùbëtni nocë. Òstani z Bògã! PS. Jakbyś czegoś nie rozumiał z tego, co wyżej napisałem, śmiało napisz, a ja to ci to napiszę po polsku. [[Brëkòwnik:RemusWej|RemusWej]] ([[Diskùsëjô brëkòwnika:RemusWej|diskùsëjô]]) 01:29, 20 łżë 2026 (CEST) :Mòjn ë ni mô sprawë, panie Jakùbie! Przédnã przëczënã, dlôcze przëznôł pòczestny gwiôzdë za stwòrzenié zacht szablónów i zbògacënié kaszëbsczi Wikipedie je, że w kaszëbsczi Wikipedie, tak jak wszelejaczé wersje môlowëch jãzëków nicht nie przëznôlë zwëkòwich abò wiãkszëch brëkòwników nôdgrodë, żebë mòtiwòwac i òstac na długò, a pózni przestôlë prôcowac dlô kaszëbsczi Wikipedie i òpùscëc to całowno. Dlôtegò pòstanowił przëznac dlô Cebie gwiôzdë, żëbë mòtiwòwac i òstac na długò. Òczëwisce ni mògã dużo gadac z tim, co napisôł. Bóg zapłac za stwòrzenié dlô mie diskùsje, bò z tim mie ceszy :) I żëczë bëlno zdrowiégò i ùcemiãgã za stwòrzenié taczé fejn cuda. (wëbaczë za pisënkòwé felë) Gòrąco pòzdrôwióm, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 12:05, 20 łżë 2026 (CEST) ::Jô téż pòzdrówiôm. Le co do mòjégò pëtaniégò niżi, wëdôlmaczã sprawã pò pòlskù, abë bëło lżi to bëlno zrozmiec. ::Zauważyłem, że fajnie zbudowałeś swoją stronę użytkownika na kaszubskiej Wikipedii. Bardzo mi podoba rozwiązanie, jakiego użyłeś, by to usystematyzować. Pytałem zaś o to, czy mógłbym w przyszłości użyć twojego kodu przy tworzeniu moich osobistych stron użytkownika? Myślę tu głównie o stronie na polską Wikipedię (cóż, jako że tam również coś czasem napiszę lub zedytuję, wypadałoby, abym w końcu ją stworzył XD),oraz anglojęzyczną, ale nie wykluczam też w przyszłości przebudowy tej już stworzonej przeze mnie na kaszubskiej Wikipedii. [[Brëkòwnik:RemusWej|RemusWej]] ([[Diskùsëjô brëkòwnika:RemusWej|diskùsëjô]]) 13:05, 21 łżë 2026 (CEST) == Pòdzãkòwanié za przeniosbã == Wiôldżé Bóg zapłac Cë za przeniesenié artikla ò Mòdżorów Pioterze na jegò docélowi môl. Jô jem baro rôd, eż mòja robòta je òbzéranô a docenianiô bez jinszich wikipedistów. Z drëdżi starnë jô baro przeprôszóm za felënk zdrzódłów. To, eż jô jesz jich ni miôł dodóné, bëło jedurnym pòwòdã, dlô jaczégò articzel béł jesz w zapisownikù. Wczorô miôł jem to wnetka zrobióné, le pò czilkù gòdzënach zmërkôł jem, eż jô nie zapisôł wińdzeniégò swòji robòtë, a jakno eż mòji laptop sã aùtomaticzno zrestartowôł, stracëł jem wszëtczé brzôdë mòji robòtë z ùzdrzódłowianiém. Terôzka jô mdã te zdrzódła dodawac òd zôczątkù a jô mdã miec starã, abë jesz dzys ònë bëłë dodóné. Jesz rôz dzãkã a żëczã jem milëczni majówczi. :) [[Brëkòwnik:RemusWej|RemusWej]] ([[Diskùsëjô brëkòwnika:RemusWej|diskùsëjô]]) 12:17, 1 môj 2026 (CEST) :Mòjn ë ni mô sprawë! Przédnã przëczënã, dlôcze przeniosł twój zapisownik-articzel ò Mòdżorów Pioterze na jegò docélowi môl, to że twój zapisownik wëzdrzi jak na dłudżi i òsoblëwi, pòniewôż w kaszëbsczi Wikipedie mómë za mało dłudżich articzlów, bò ni chcą pòswiãcëwac przënômni 30% czasu na editowanié i dofùlowiwanié kaszëbsczich rzeczy, dopòczi staną sã niaktualné i stôré. Dlôte pòstanowił to robic. Òczëwisce w dodôtkù dodôł wiki-fónkcje taczich jak Commons, kategòrie i zacht jinszëch i kòrektowôł je. Nic sã ni stało, że ni dodôł u Cebie zdrzódłów, bò nierôz mògą bëc problemë. Òczëwisce czejbë chcesz zdolmaczëc interfejs MediaWiki na kaszëbsczi (bò stwòrzëł w Karczmie diskùsje), klëkni tuwò: [https://translatewiki.net TranslateWiki]! Dzãka za stwòrzenié diskùsje ò mie :) Cebie téż żëczã jem milëczni majówczi i Òstani z Bògã! Pòzdrówióm, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 12:59, 1 môj 2026 (CEST) == Słów czile == Mòjn! Miło je widzec, że persónë taczé jak të, chcą sã angażowac i robic dlô kaszëbiznë na tak apartny métel jak rozwij Wikipedie – móm dlô ce bédënk, cobë më z @[[Brëkòwnik:Rzadtymczasowy|Rzadtymczasowy]] mòglë bëc z Tobą w łączbie, pòmagac (np. jinyma słowarzama niż niedoskònałi sloworz.org) i dzejac mët, môsz të Discorda abò cos jinszégò do kòmùnikacje? [[Brëkòwnik:Terrus38|Terrus38]] ([[Diskùsëjô brëkòwnika:Terrus38|diskùsëjô]]) 15:35, 15 cze 2026 (CEST) :Witam, panie Terrus! Na razie nie będę mówił po kaszubsku, bo słowniki kaszubskie w internecie są jeszcze ograniczone, ale na końcu wstawię pozdrowienie i podziękowanie do Ciebie. Dziękuję za poprawki gramatyczne moich tłumaczeń w TranslateWiki. I oczywiście odkąd zrobiłeś dzisiaj dyskusję, tak, ja zaangażowałem do dyskusji dotyczące gramatyki kaszubszczyzny, bo pan i twoi koledzy robicie małą debatę. Mówiąc zaangażowaniu, będę podawał kroki: :# Nie posiadam żadnych komunikatorów, bo komunikatory według mnie są nieprzydatne i niepotrzebne, e-mail też. :# Na razie wolę robić edycje w kaszubskiej Wikipedii i je rozbudować. :PS: Napisałem odrobinę profesjonalnie, bo tak muszę. Dzãkùjã baro za stwòrzenié diskùsje dlô mie i serdeczno pòzdrówióm, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 16:22, 15 cze 2026 (CEST) </div> == Òddzãkòwanié == Adjé, Orbitminis, dzãka za Twój wkłôd we Wikipediã i żëczã wszëtczégò bëlnégò! Jô ale prosto pòprôwiôł pisënk a sztilistikã pò pòprzédnëch edicjach, jô nie rëmôł bezpòtrzébno akapitów encyklopedicznégò tekstu, jaczi pisalë jiny, czë Të, czë jinszi. Jô nie jem Tobie za wroga, a za wespółrobòtnika. W kòżdim razu, pòzdrówk. [[Brëkòwnik:Terrus38|Terrus38]] ([[Diskùsëjô brëkòwnika:Terrus38|diskùsëjô]]) 14:50, 5 lëp 2026 (CEST) :Witôjtaż baro serdeczno, panie Terrusie! :Ostatnio zakończyłem/zawiesiłem moją działalność na zawsze ze względu, że nie chciałem przez całe lato i życie edytować cały czas na kaszubskiej Wikipedii z powodu znajomości i wolnego stylu edytowania korzystając ze słowników takich jak wspomniany przez Ciebie wcześniej „niedokładnego [[Internetowi Słowôrz Kaszëbsczégò Jãzëka|Internetowégò Słowarza Kaszëbsczégò Jãzëka]]” i Glosbe (przejście polsko-kaszubskie). Ja chciałem tylko zbogacić, a unowocześnić waszą encyklopedię polegając informacji z polskiej Wikipedii jako hobby albo cel do uratowania mniejszościowych języków Polski (ostatnio rozszerzyłem informacje o religijnych postaciach [[Mirosłôw Adamczik|Mirosława Adamczyka]] i [[Wòjcech Cëchòsz|Wòjcecha Cëchòsza]]) i podczas wybuchu ilości moich wkładów dodałem sporo własnych i samodzielnych informacji w artykule [[Méster Bëlnégò Czëtaniô (kònkùrs)|Méster Bëlnégò Czëtaniô]] z przepisami. I nie tylko poprawiłeś niektóre moje edycje, ale także użyłeś mojego userboksa (potocznie kaszubskie: ''brëkòkôstka'') dotyczące o małą ilością artykułów w waszej encyklopedii przed usunięciem mojej strony, ale mimo wszystkiego, czasami albo rzadko robię problemy z przeniesieniami i ortografią ze względu trudności, leksyki i mniejszą ilością zasobów kaszubskiego języka w Internecie (mimo że np. w Youtubie jest ich dużo filmików i sporo kanałów po kaszubsku). :Dziękuję panu za twoje pańskie miłe słowa, stworzenie dyskusji i uznawanie mnie za współpracownika, bo spędziłem dokładnie rok i pół czasu, żeby zbudować i poświęcić na tworzenie moich tłumaczeń kaszubskich do artykułów i szablonów! (przełączę na kaszubski) Cebie téż żëczã wszëtczégò bëlnégò i dalszégò rozwiju waji encyklopedie i mileczni, robòcy ferie, bò jes cëdownym editorã, chtëren chcôł zlepszëc i rëmąc wszelczé problemë nie leno techniczno, le téż òrtograficzno waji encyklopedie, żebë Kaszëbskô Wikipediô stała sã wikszim i encyklopedicznym placã pò kaszëbskù. :PS. Przepraszam za błędy ortograficzne w języku kaszubskim, bo korzystałem słowniki. :Gòrący pòzdrówk, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 15:51, 5 lëp 2026 (CEST) qqdlus2rb738wu1cum5380dy2d5gte1 204446 204445 2026-07-05T13:53:44Z Orbitminis 18250 drob. 204446 wikitext text/x-wiki <div align="center" class="usermessage">Witôjtaż w mòji diskùsji brëkòwnika! Mòżesz téż pisac pò kaszëbskù, pò pòlskù, pò anielskù i pò miemiecckù, a pònemù bãdã òdpòwiadał. ----[[Òbrôzk:Sandbox.svg|lewo|bezramki|78x78px]][[Brëkòwnik:Orbitminis/Zapisownik|<big>ZAPISOWNIK</big>]] [[Brëkòwnik:Orbitminis|<big>Ò MIE</big>]] [[Òbrôzk:Debate,_discussion.jpg|bezramki|195x195px]] '''<big>DISKÙSËJÔ BRËKÒWNIKA</big>''' </div> <div align="center" class="disscusion"> == Poprawanie linków == Hej. Nie musisz wykonywać takich poprawek, jak <tt><nowiki>[[Gduńsk]]ù -> [[Gduńsk|Gduńskù]]</nowiki></tt> – w najbliższych dniach (może tygodniach) konfiguracja linków zostanie naprawiona na&nbsp;poziomie całej wiki. Pozdrawiam, [[Brëkòwnik:Tsca|tsca]] ([[Diskùsëjô brëkòwnika:Tsca|diskùsëjô]]) 21:01, 31 zél 2025 (CEST) :Wiem, proszę pana. Ale ja muszę poprawiać kaszubską Wikipedię, żeby nie były takich błędów ortograficznych jak "[[Gduńsk]]ù" i "Jop Polsczei jaczieego", bo te błędy mogą sprawiać kaszubską Wikipedię jako encyklopedię trzeciego świata jak najmniejsze Wikipedie w różnych językach. I oczywiście te źródła, artykuły, samouczki i szablony niektóre wyglądają staro jak w latach 2000-2010, więc muszą mieć nowe źródła, świeże infomacje i szablony podobne do polskiej Wikipedii po kaszubsku. Więc przepraszam najmocniej za takie, które spełniłem. Pozdrawiam, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 10:12, 1 Séw 2025 (CEST) == Automatycznie tłumaczone artykuły == Wklejanie automatycznie tłumaczonych artykułów mija się z celem. Skoro, jak sam deklarujesz na swojej stronie użytkownika, nie znasz języka kaszubskiego, nie jesteś w stanie zauważyć ani poprawić błędów w maszynowo przetłumaczonych treściach. Hasła utworzone w ten sposób niestety muszą zostać usunięte. [[Brëkòwnik:Tsca|tsca]] ([[Diskùsëjô brëkòwnika:Tsca|diskùsëjô]]) 23:28, 29 ruj 2025 (CET) :czemu? [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 16:11, 18 lës 2025 (CET) ::CSB: Mòjn panie Tsca! Dzysë jô sã dowiedzôł twòją nôùczkã, żëbë nie wlëmic "aùtomaticznie dolmaczonëch artiklów". Wiãc pòstanowił ùżëc słowôrza do stwórzbë artiklów, żëbë bëło trochã lepszé artiklów z pasëjącą pisënką. Żle w mojich artiklach nalézają sã felë, dôj mni znac. Pozdrôwióm, Orbitminis. ::PL: Cześć, panie Tsca! Dzisiaj ja się dowiedziałem twoją nauczkę, żeby nie wklejać „automatycznie tłumaczonych artykułów”. Więc postanowiłem użyć słownika do stworzenia artykułów, żeby było trochę lepsze artykułów z odpowiednią ortografią. Jeżeli w moich artykułów znajdują się błędy, daj mnie znać. Pozdrawiam, ::[[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 12:45, 9 gro 2026 (CET) == Odpowiedź == Cześć, po pierwsze, wybacz, że nie zareagowałem na [[Diskùsëjô brëkòwnika:DawnyTest#Szablónë pòtrzébùją zmianë ë aktualizacëji|twoją starą wiadomość]] o dyskusji na naszej wiki. W pełni się zgadzam, iż zmienione muszą zostać szablony, artykuły itp. Próbowałem podjąć się [[Szablóna:Infobox pòlitik|trudu ich tworzenia]] i edytowania (konkretnie infoboksów). Takowym był ''[[Szablóna:Infobox-państwò]]'', który w mojej opinii posiadał pewne problemy, które udało mi się zaleczyć. Posiłkowałem się m.in. polską wiki i tak na przykład mamy tam rewolucyjny wynalazek opcjonalnych parametrów. Po drugie, odnosząc się do twojej wiadomości, czy jest coś konkretnego co, twoim zdaniem, dobrze by było zmienić w szablonach lub ewentualnie by jakieś szablony utworzyć? Jestem chętny by podjąć się współpracy. Pozdrawiam cieplutko.<br> <small>A po polsku odpisałem, bo mi było szybciej, nie znam kaszubskiego na tyle, by bezbłędnie wytworzyć taką wiadomość w tak krótkim czasie.</small> [[Brëkòwnik:DawnyTest|DawnyTest]] ([[Diskùsëjô brëkòwnika:DawnyTest|diskùsëjô]]) 21:25, 18 str 2026 (CET) == Pòdzãkòwanié a pëtanié == Laudetur Iesus Christus et Semper Virgo Maria! Jô bëm chcôł Cë baro pòdzãkòwac za docenienié mòji robòtë z ùsôdzaniém wiadłokôstków dlô kaszëbsczi Wikipedëje a przëznanié mie pòczestny gwiôzdë. Hewò je dlô mie wiôlgą bùchą, eż jô mògã na taczé mòdło czënic nen môl jesz lepszim a snôższim. Takô deja je mie blizą òb robienié nowich szablonów a robòtã nad artiklama, co do jaczich wiedno jô móm starã, abë bëłë jak nôkwalitetniészé ë jak nôbarżi rozkòscérzóné. Przi ti lëżnoscë jô bëm chcôł Cë ò jedno zapëtôc. Jô trafił niedôwno na twòją starnã brëkòwnikã ë jô móm wiôldżé ùwôżanié do tewò, jak Të fejn to zrobił. Mògã jô ùżëc twòjéwò kòdù do swòjich starnów brëkòwnikã. Jô w nôblëższim czasu chcã zabrac sã za ji ùsôdzenié na pòlsczi a anielsczi Wikipedëjë, a téż jô nie òdsôdzôm ùprawieniégò ë przebùdérowaniéwò téż mòji starnë brëkòwnika z kaszëbsczi Wikipedëjë. Jô z górë dzãkùjã, pòzdrówiôm a żëczã bëlni, ùbëtni nocë. Òstani z Bògã! PS. Jakbyś czegoś nie rozumiał z tego, co wyżej napisałem, śmiało napisz, a ja to ci to napiszę po polsku. [[Brëkòwnik:RemusWej|RemusWej]] ([[Diskùsëjô brëkòwnika:RemusWej|diskùsëjô]]) 01:29, 20 łżë 2026 (CEST) :Mòjn ë ni mô sprawë, panie Jakùbie! Przédnã przëczënã, dlôcze przëznôł pòczestny gwiôzdë za stwòrzenié zacht szablónów i zbògacënié kaszëbsczi Wikipedie je, że w kaszëbsczi Wikipedie, tak jak wszelejaczé wersje môlowëch jãzëków nicht nie przëznôlë zwëkòwich abò wiãkszëch brëkòwników nôdgrodë, żebë mòtiwòwac i òstac na długò, a pózni przestôlë prôcowac dlô kaszëbsczi Wikipedie i òpùscëc to całowno. Dlôtegò pòstanowił przëznac dlô Cebie gwiôzdë, żëbë mòtiwòwac i òstac na długò. Òczëwisce ni mògã dużo gadac z tim, co napisôł. Bóg zapłac za stwòrzenié dlô mie diskùsje, bò z tim mie ceszy :) I żëczë bëlno zdrowiégò i ùcemiãgã za stwòrzenié taczé fejn cuda. (wëbaczë za pisënkòwé felë) Gòrąco pòzdrôwióm, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 12:05, 20 łżë 2026 (CEST) ::Jô téż pòzdrówiôm. Le co do mòjégò pëtaniégò niżi, wëdôlmaczã sprawã pò pòlskù, abë bëło lżi to bëlno zrozmiec. ::Zauważyłem, że fajnie zbudowałeś swoją stronę użytkownika na kaszubskiej Wikipedii. Bardzo mi podoba rozwiązanie, jakiego użyłeś, by to usystematyzować. Pytałem zaś o to, czy mógłbym w przyszłości użyć twojego kodu przy tworzeniu moich osobistych stron użytkownika? Myślę tu głównie o stronie na polską Wikipedię (cóż, jako że tam również coś czasem napiszę lub zedytuję, wypadałoby, abym w końcu ją stworzył XD),oraz anglojęzyczną, ale nie wykluczam też w przyszłości przebudowy tej już stworzonej przeze mnie na kaszubskiej Wikipedii. [[Brëkòwnik:RemusWej|RemusWej]] ([[Diskùsëjô brëkòwnika:RemusWej|diskùsëjô]]) 13:05, 21 łżë 2026 (CEST) == Pòdzãkòwanié za przeniosbã == Wiôldżé Bóg zapłac Cë za przeniesenié artikla ò Mòdżorów Pioterze na jegò docélowi môl. Jô jem baro rôd, eż mòja robòta je òbzéranô a docenianiô bez jinszich wikipedistów. Z drëdżi starnë jô baro przeprôszóm za felënk zdrzódłów. To, eż jô jesz jich ni miôł dodóné, bëło jedurnym pòwòdã, dlô jaczégò articzel béł jesz w zapisownikù. Wczorô miôł jem to wnetka zrobióné, le pò czilkù gòdzënach zmërkôł jem, eż jô nie zapisôł wińdzeniégò swòji robòtë, a jakno eż mòji laptop sã aùtomaticzno zrestartowôł, stracëł jem wszëtczé brzôdë mòji robòtë z ùzdrzódłowianiém. Terôzka jô mdã te zdrzódła dodawac òd zôczątkù a jô mdã miec starã, abë jesz dzys ònë bëłë dodóné. Jesz rôz dzãkã a żëczã jem milëczni majówczi. :) [[Brëkòwnik:RemusWej|RemusWej]] ([[Diskùsëjô brëkòwnika:RemusWej|diskùsëjô]]) 12:17, 1 môj 2026 (CEST) :Mòjn ë ni mô sprawë! Przédnã przëczënã, dlôcze przeniosł twój zapisownik-articzel ò Mòdżorów Pioterze na jegò docélowi môl, to że twój zapisownik wëzdrzi jak na dłudżi i òsoblëwi, pòniewôż w kaszëbsczi Wikipedie mómë za mało dłudżich articzlów, bò ni chcą pòswiãcëwac przënômni 30% czasu na editowanié i dofùlowiwanié kaszëbsczich rzeczy, dopòczi staną sã niaktualné i stôré. Dlôte pòstanowił to robic. Òczëwisce w dodôtkù dodôł wiki-fónkcje taczich jak Commons, kategòrie i zacht jinszëch i kòrektowôł je. Nic sã ni stało, że ni dodôł u Cebie zdrzódłów, bò nierôz mògą bëc problemë. Òczëwisce czejbë chcesz zdolmaczëc interfejs MediaWiki na kaszëbsczi (bò stwòrzëł w Karczmie diskùsje), klëkni tuwò: [https://translatewiki.net TranslateWiki]! Dzãka za stwòrzenié diskùsje ò mie :) Cebie téż żëczã jem milëczni majówczi i Òstani z Bògã! Pòzdrówióm, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 12:59, 1 môj 2026 (CEST) == Słów czile == Mòjn! Miło je widzec, że persónë taczé jak të, chcą sã angażowac i robic dlô kaszëbiznë na tak apartny métel jak rozwij Wikipedie – móm dlô ce bédënk, cobë më z @[[Brëkòwnik:Rzadtymczasowy|Rzadtymczasowy]] mòglë bëc z Tobą w łączbie, pòmagac (np. jinyma słowarzama niż niedoskònałi sloworz.org) i dzejac mët, môsz të Discorda abò cos jinszégò do kòmùnikacje? [[Brëkòwnik:Terrus38|Terrus38]] ([[Diskùsëjô brëkòwnika:Terrus38|diskùsëjô]]) 15:35, 15 cze 2026 (CEST) :Witam, panie Terrus! Na razie nie będę mówił po kaszubsku, bo słowniki kaszubskie w internecie są jeszcze ograniczone, ale na końcu wstawię pozdrowienie i podziękowanie do Ciebie. Dziękuję za poprawki gramatyczne moich tłumaczeń w TranslateWiki. I oczywiście odkąd zrobiłeś dzisiaj dyskusję, tak, ja zaangażowałem do dyskusji dotyczące gramatyki kaszubszczyzny, bo pan i twoi koledzy robicie małą debatę. Mówiąc zaangażowaniu, będę podawał kroki: :# Nie posiadam żadnych komunikatorów, bo komunikatory według mnie są nieprzydatne i niepotrzebne, e-mail też. :# Na razie wolę robić edycje w kaszubskiej Wikipedii i je rozbudować. :PS: Napisałem odrobinę profesjonalnie, bo tak muszę. Dzãkùjã baro za stwòrzenié diskùsje dlô mie i serdeczno pòzdrówióm, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 16:22, 15 cze 2026 (CEST) </div> == Òddzãkòwanié == Adjé, Orbitminis, dzãka za Twój wkłôd we Wikipediã i żëczã wszëtczégò bëlnégò! Jô ale prosto pòprôwiôł pisënk a sztilistikã pò pòprzédnëch edicjach, jô nie rëmôł bezpòtrzébno akapitów encyklopedicznégò tekstu, jaczi pisalë jiny, czë Të, czë jinszi. Jô nie jem Tobie za wroga, a za wespółrobòtnika. W kòżdim razu, pòzdrówk. [[Brëkòwnik:Terrus38|Terrus38]] ([[Diskùsëjô brëkòwnika:Terrus38|diskùsëjô]]) 14:50, 5 lëp 2026 (CEST) :Witôjtaż baro serdeczno, panie Terrusie! :Ostatnio zakończyłem/zawiesiłem moją działalność na zawsze ze względu, że nie chciałem przez całe lato i życie edytować cały czas na kaszubskiej Wikipedii z powodu znajomości i wolnego stylu edytowania korzystając ze słowników takich jak wspomniany przez Ciebie wcześniej „niedokładnego [[Internetowi Słowôrz Kaszëbsczégò Jãzëka|Internetowégò Słowarza Kaszëbsczégò Jãzëka]]” i Glosbe (przejście polsko-kaszubskie). Ja chciałem tylko zbogacić, a unowocześnić waszą encyklopedię polegając informacji z polskiej Wikipedii jako hobby albo cel do uratowania mniejszościowych języków Polski (ostatnio rozszerzyłem informacje o religijnych postaciach [[Mirosłôw Adamczik|Mirosława Adamczyka]] i [[Wòjcech Cëchòsz|Wòjcecha Cëchòsza]]) i podczas wybuchu ilości moich wkładów dodałem sporo własnych i samodzielnych informacji w artykule [[Méster Bëlnégò Czëtaniô (kònkùrs)|Méster Bëlnégò Czëtaniô]] z przepisami. I nie tylko poprawiłeś niektóre moje edycje, ale także użyłeś mojego userboksa (potocznie kaszubskie: ''brëkòkôstka'') dotyczące o małą ilością artykułów w waszej encyklopedii przed usunięciem mojej strony, ale mimo wszystkiego, czasami albo rzadko robię problemy z przeniesieniami i ortografią ze względu trudności, leksyki i mniejszą ilością zasobów kaszubskiego języka w Internecie (mimo że np. w Youtubie jest ich dużo filmików i sporo kanałów po kaszubsku). :Dziękuję panu za twoje pańskie miłe słowa, stworzenie dyskusji i uznawanie mnie za współpracownika, bo spędziłem dokładnie rok i pół czasu, żeby zbudować i poświęcić na tworzenie moich tłumaczeń kaszubskich do artykułów i szablonów! (przełączę na kaszubski) Cebie téż żëczã wszëtczégò bëlnégò i dalszégò rozwiju waji encyklopedie i mileczni, robòcy ferie, bò jes cëdownym editorã, chtëren chcôł zlepszëc i rëmąc wszelczé problemë nie leno techniczno, le téż òrtograficzno waji encyklopedie, żebë Kaszëbskô Wikipediô stała sã wikszim i encyklopedicznym placã pò kaszëbskù. :PS. Przepraszam za błędy ortograficzne w języku kaszubskim, bo korzystałem słowniki. :Gòrący pòzdrówk, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 15:51, 5 lëp 2026 (CEST) </div> c8fhtcjdmbrcqrr0aleztq2yx7e0s32 204465 204446 2026-07-05T15:30:20Z Orbitminis 18250 /* Òddzãkòwanié */ Odpowiedź 204465 wikitext text/x-wiki <div align="center" class="usermessage">Witôjtaż w mòji diskùsji brëkòwnika! Mòżesz téż pisac pò kaszëbskù, pò pòlskù, pò anielskù i pò miemiecckù, a pònemù bãdã òdpòwiadał. ----[[Òbrôzk:Sandbox.svg|lewo|bezramki|78x78px]][[Brëkòwnik:Orbitminis/Zapisownik|<big>ZAPISOWNIK</big>]] [[Brëkòwnik:Orbitminis|<big>Ò MIE</big>]] [[Òbrôzk:Debate,_discussion.jpg|bezramki|195x195px]] '''<big>DISKÙSËJÔ BRËKÒWNIKA</big>''' </div> <div align="center" class="disscusion"> == Poprawanie linków == Hej. Nie musisz wykonywać takich poprawek, jak <tt><nowiki>[[Gduńsk]]ù -> [[Gduńsk|Gduńskù]]</nowiki></tt> – w najbliższych dniach (może tygodniach) konfiguracja linków zostanie naprawiona na&nbsp;poziomie całej wiki. Pozdrawiam, [[Brëkòwnik:Tsca|tsca]] ([[Diskùsëjô brëkòwnika:Tsca|diskùsëjô]]) 21:01, 31 zél 2025 (CEST) :Wiem, proszę pana. Ale ja muszę poprawiać kaszubską Wikipedię, żeby nie były takich błędów ortograficznych jak "[[Gduńsk]]ù" i "Jop Polsczei jaczieego", bo te błędy mogą sprawiać kaszubską Wikipedię jako encyklopedię trzeciego świata jak najmniejsze Wikipedie w różnych językach. I oczywiście te źródła, artykuły, samouczki i szablony niektóre wyglądają staro jak w latach 2000-2010, więc muszą mieć nowe źródła, świeże infomacje i szablony podobne do polskiej Wikipedii po kaszubsku. Więc przepraszam najmocniej za takie, które spełniłem. Pozdrawiam, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 10:12, 1 Séw 2025 (CEST) == Automatycznie tłumaczone artykuły == Wklejanie automatycznie tłumaczonych artykułów mija się z celem. Skoro, jak sam deklarujesz na swojej stronie użytkownika, nie znasz języka kaszubskiego, nie jesteś w stanie zauważyć ani poprawić błędów w maszynowo przetłumaczonych treściach. Hasła utworzone w ten sposób niestety muszą zostać usunięte. [[Brëkòwnik:Tsca|tsca]] ([[Diskùsëjô brëkòwnika:Tsca|diskùsëjô]]) 23:28, 29 ruj 2025 (CET) :czemu? [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 16:11, 18 lës 2025 (CET) ::CSB: Mòjn panie Tsca! Dzysë jô sã dowiedzôł twòją nôùczkã, żëbë nie wlëmic "aùtomaticznie dolmaczonëch artiklów". Wiãc pòstanowił ùżëc słowôrza do stwórzbë artiklów, żëbë bëło trochã lepszé artiklów z pasëjącą pisënką. Żle w mojich artiklach nalézają sã felë, dôj mni znac. Pozdrôwióm, Orbitminis. ::PL: Cześć, panie Tsca! Dzisiaj ja się dowiedziałem twoją nauczkę, żeby nie wklejać „automatycznie tłumaczonych artykułów”. Więc postanowiłem użyć słownika do stworzenia artykułów, żeby było trochę lepsze artykułów z odpowiednią ortografią. Jeżeli w moich artykułów znajdują się błędy, daj mnie znać. Pozdrawiam, ::[[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 12:45, 9 gro 2026 (CET) == Odpowiedź == Cześć, po pierwsze, wybacz, że nie zareagowałem na [[Diskùsëjô brëkòwnika:DawnyTest#Szablónë pòtrzébùją zmianë ë aktualizacëji|twoją starą wiadomość]] o dyskusji na naszej wiki. W pełni się zgadzam, iż zmienione muszą zostać szablony, artykuły itp. Próbowałem podjąć się [[Szablóna:Infobox pòlitik|trudu ich tworzenia]] i edytowania (konkretnie infoboksów). Takowym był ''[[Szablóna:Infobox-państwò]]'', który w mojej opinii posiadał pewne problemy, które udało mi się zaleczyć. Posiłkowałem się m.in. polską wiki i tak na przykład mamy tam rewolucyjny wynalazek opcjonalnych parametrów. Po drugie, odnosząc się do twojej wiadomości, czy jest coś konkretnego co, twoim zdaniem, dobrze by było zmienić w szablonach lub ewentualnie by jakieś szablony utworzyć? Jestem chętny by podjąć się współpracy. Pozdrawiam cieplutko.<br> <small>A po polsku odpisałem, bo mi było szybciej, nie znam kaszubskiego na tyle, by bezbłędnie wytworzyć taką wiadomość w tak krótkim czasie.</small> [[Brëkòwnik:DawnyTest|DawnyTest]] ([[Diskùsëjô brëkòwnika:DawnyTest|diskùsëjô]]) 21:25, 18 str 2026 (CET) == Pòdzãkòwanié a pëtanié == Laudetur Iesus Christus et Semper Virgo Maria! Jô bëm chcôł Cë baro pòdzãkòwac za docenienié mòji robòtë z ùsôdzaniém wiadłokôstków dlô kaszëbsczi Wikipedëje a przëznanié mie pòczestny gwiôzdë. Hewò je dlô mie wiôlgą bùchą, eż jô mògã na taczé mòdło czënic nen môl jesz lepszim a snôższim. Takô deja je mie blizą òb robienié nowich szablonów a robòtã nad artiklama, co do jaczich wiedno jô móm starã, abë bëłë jak nôkwalitetniészé ë jak nôbarżi rozkòscérzóné. Przi ti lëżnoscë jô bëm chcôł Cë ò jedno zapëtôc. Jô trafił niedôwno na twòją starnã brëkòwnikã ë jô móm wiôldżé ùwôżanié do tewò, jak Të fejn to zrobił. Mògã jô ùżëc twòjéwò kòdù do swòjich starnów brëkòwnikã. Jô w nôblëższim czasu chcã zabrac sã za ji ùsôdzenié na pòlsczi a anielsczi Wikipedëjë, a téż jô nie òdsôdzôm ùprawieniégò ë przebùdérowaniéwò téż mòji starnë brëkòwnika z kaszëbsczi Wikipedëjë. Jô z górë dzãkùjã, pòzdrówiôm a żëczã bëlni, ùbëtni nocë. Òstani z Bògã! PS. Jakbyś czegoś nie rozumiał z tego, co wyżej napisałem, śmiało napisz, a ja to ci to napiszę po polsku. [[Brëkòwnik:RemusWej|RemusWej]] ([[Diskùsëjô brëkòwnika:RemusWej|diskùsëjô]]) 01:29, 20 łżë 2026 (CEST) :Mòjn ë ni mô sprawë, panie Jakùbie! Przédnã przëczënã, dlôcze przëznôł pòczestny gwiôzdë za stwòrzenié zacht szablónów i zbògacënié kaszëbsczi Wikipedie je, że w kaszëbsczi Wikipedie, tak jak wszelejaczé wersje môlowëch jãzëków nicht nie przëznôlë zwëkòwich abò wiãkszëch brëkòwników nôdgrodë, żebë mòtiwòwac i òstac na długò, a pózni przestôlë prôcowac dlô kaszëbsczi Wikipedie i òpùscëc to całowno. Dlôtegò pòstanowił przëznac dlô Cebie gwiôzdë, żëbë mòtiwòwac i òstac na długò. Òczëwisce ni mògã dużo gadac z tim, co napisôł. Bóg zapłac za stwòrzenié dlô mie diskùsje, bò z tim mie ceszy :) I żëczë bëlno zdrowiégò i ùcemiãgã za stwòrzenié taczé fejn cuda. (wëbaczë za pisënkòwé felë) Gòrąco pòzdrôwióm, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 12:05, 20 łżë 2026 (CEST) ::Jô téż pòzdrówiôm. Le co do mòjégò pëtaniégò niżi, wëdôlmaczã sprawã pò pòlskù, abë bëło lżi to bëlno zrozmiec. ::Zauważyłem, że fajnie zbudowałeś swoją stronę użytkownika na kaszubskiej Wikipedii. Bardzo mi podoba rozwiązanie, jakiego użyłeś, by to usystematyzować. Pytałem zaś o to, czy mógłbym w przyszłości użyć twojego kodu przy tworzeniu moich osobistych stron użytkownika? Myślę tu głównie o stronie na polską Wikipedię (cóż, jako że tam również coś czasem napiszę lub zedytuję, wypadałoby, abym w końcu ją stworzył XD),oraz anglojęzyczną, ale nie wykluczam też w przyszłości przebudowy tej już stworzonej przeze mnie na kaszubskiej Wikipedii. [[Brëkòwnik:RemusWej|RemusWej]] ([[Diskùsëjô brëkòwnika:RemusWej|diskùsëjô]]) 13:05, 21 łżë 2026 (CEST) == Pòdzãkòwanié za przeniosbã == Wiôldżé Bóg zapłac Cë za przeniesenié artikla ò Mòdżorów Pioterze na jegò docélowi môl. Jô jem baro rôd, eż mòja robòta je òbzéranô a docenianiô bez jinszich wikipedistów. Z drëdżi starnë jô baro przeprôszóm za felënk zdrzódłów. To, eż jô jesz jich ni miôł dodóné, bëło jedurnym pòwòdã, dlô jaczégò articzel béł jesz w zapisownikù. Wczorô miôł jem to wnetka zrobióné, le pò czilkù gòdzënach zmërkôł jem, eż jô nie zapisôł wińdzeniégò swòji robòtë, a jakno eż mòji laptop sã aùtomaticzno zrestartowôł, stracëł jem wszëtczé brzôdë mòji robòtë z ùzdrzódłowianiém. Terôzka jô mdã te zdrzódła dodawac òd zôczątkù a jô mdã miec starã, abë jesz dzys ònë bëłë dodóné. Jesz rôz dzãkã a żëczã jem milëczni majówczi. :) [[Brëkòwnik:RemusWej|RemusWej]] ([[Diskùsëjô brëkòwnika:RemusWej|diskùsëjô]]) 12:17, 1 môj 2026 (CEST) :Mòjn ë ni mô sprawë! Przédnã przëczënã, dlôcze przeniosł twój zapisownik-articzel ò Mòdżorów Pioterze na jegò docélowi môl, to że twój zapisownik wëzdrzi jak na dłudżi i òsoblëwi, pòniewôż w kaszëbsczi Wikipedie mómë za mało dłudżich articzlów, bò ni chcą pòswiãcëwac przënômni 30% czasu na editowanié i dofùlowiwanié kaszëbsczich rzeczy, dopòczi staną sã niaktualné i stôré. Dlôte pòstanowił to robic. Òczëwisce w dodôtkù dodôł wiki-fónkcje taczich jak Commons, kategòrie i zacht jinszëch i kòrektowôł je. Nic sã ni stało, że ni dodôł u Cebie zdrzódłów, bò nierôz mògą bëc problemë. Òczëwisce czejbë chcesz zdolmaczëc interfejs MediaWiki na kaszëbsczi (bò stwòrzëł w Karczmie diskùsje), klëkni tuwò: [https://translatewiki.net TranslateWiki]! Dzãka za stwòrzenié diskùsje ò mie :) Cebie téż żëczã jem milëczni majówczi i Òstani z Bògã! Pòzdrówióm, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 12:59, 1 môj 2026 (CEST) == Słów czile == Mòjn! Miło je widzec, że persónë taczé jak të, chcą sã angażowac i robic dlô kaszëbiznë na tak apartny métel jak rozwij Wikipedie – móm dlô ce bédënk, cobë më z @[[Brëkòwnik:Rzadtymczasowy|Rzadtymczasowy]] mòglë bëc z Tobą w łączbie, pòmagac (np. jinyma słowarzama niż niedoskònałi sloworz.org) i dzejac mët, môsz të Discorda abò cos jinszégò do kòmùnikacje? [[Brëkòwnik:Terrus38|Terrus38]] ([[Diskùsëjô brëkòwnika:Terrus38|diskùsëjô]]) 15:35, 15 cze 2026 (CEST) :Witam, panie Terrus! Na razie nie będę mówił po kaszubsku, bo słowniki kaszubskie w internecie są jeszcze ograniczone, ale na końcu wstawię pozdrowienie i podziękowanie do Ciebie. Dziękuję za poprawki gramatyczne moich tłumaczeń w TranslateWiki. I oczywiście odkąd zrobiłeś dzisiaj dyskusję, tak, ja zaangażowałem do dyskusji dotyczące gramatyki kaszubszczyzny, bo pan i twoi koledzy robicie małą debatę. Mówiąc zaangażowaniu, będę podawał kroki: :# Nie posiadam żadnych komunikatorów, bo komunikatory według mnie są nieprzydatne i niepotrzebne, e-mail też. :# Na razie wolę robić edycje w kaszubskiej Wikipedii i je rozbudować. :PS: Napisałem odrobinę profesjonalnie, bo tak muszę. Dzãkùjã baro za stwòrzenié diskùsje dlô mie i serdeczno pòzdrówióm, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 16:22, 15 cze 2026 (CEST) </div> == Òddzãkòwanié == Adjé, Orbitminis, dzãka za Twój wkłôd we Wikipediã i żëczã wszëtczégò bëlnégò! Jô ale prosto pòprôwiôł pisënk a sztilistikã pò pòprzédnëch edicjach, jô nie rëmôł bezpòtrzébno akapitów encyklopedicznégò tekstu, jaczi pisalë jiny, czë Të, czë jinszi. Jô nie jem Tobie za wroga, a za wespółrobòtnika. W kòżdim razu, pòzdrówk. [[Brëkòwnik:Terrus38|Terrus38]] ([[Diskùsëjô brëkòwnika:Terrus38|diskùsëjô]]) 14:50, 5 lëp 2026 (CEST) :Witôjtaż baro serdeczno, panie Terrusie! :Ostatnio zakończyłem/zawiesiłem moją działalność na zawsze ze względu, że nie chciałem przez całe lato i życie edytować cały czas na kaszubskiej Wikipedii z powodu znajomości i wolnego stylu edytowania korzystając ze słowników takich jak wspomniany przez Ciebie wcześniej „niedokładnego [[Internetowi Słowôrz Kaszëbsczégò Jãzëka|Internetowégò Słowarza Kaszëbsczégò Jãzëka]]” i Glosbe (przejście polsko-kaszubskie). Ja chciałem tylko zbogacić, a unowocześnić waszą encyklopedię polegając informacji z polskiej Wikipedii jako hobby albo cel do uratowania mniejszościowych języków Polski (ostatnio rozszerzyłem informacje o religijnych postaciach [[Mirosłôw Adamczik|Mirosława Adamczyka]] i [[Wòjcech Cëchòsz|Wòjcecha Cëchòsza]]) i podczas wybuchu ilości moich wkładów dodałem sporo własnych i samodzielnych informacji w artykule [[Méster Bëlnégò Czëtaniô (kònkùrs)|Méster Bëlnégò Czëtaniô]] z przepisami. I nie tylko poprawiłeś niektóre moje edycje, ale także użyłeś mojego userboksa (potocznie kaszubskie: ''brëkòkôstka'') dotyczące o małą ilością artykułów w waszej encyklopedii przed usunięciem mojej strony, ale mimo wszystkiego, czasami albo rzadko robię problemy z przeniesieniami i ortografią ze względu trudności, leksyki i mniejszą ilością zasobów kaszubskiego języka w Internecie (mimo że np. w Youtubie jest ich dużo filmików i sporo kanałów po kaszubsku). :Dziękuję panu za twoje pańskie miłe słowa, stworzenie dyskusji i uznawanie mnie za współpracownika, bo spędziłem dokładnie rok i pół czasu, żeby zbudować i poświęcić na tworzenie moich tłumaczeń kaszubskich do artykułów i szablonów! (przełączę na kaszubski) Cebie téż żëczã wszëtczégò bëlnégò i dalszégò rozwiju waji encyklopedie i mileczni, robòcy ferie, bò jes cëdownym editorã, chtëren chcôł zlepszëc i rëmąc wszelczé problemë nie leno techniczno, le téż òrtograficzno waji encyklopedie, żebë Kaszëbskô Wikipediô stała sã wikszim i encyklopedicznym placã pò kaszëbskù. :PS. Przepraszam za błędy ortograficzne w języku kaszubskim, bo korzystałem słowniki. :Gòrący pòzdrówk, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 15:51, 5 lëp 2026 (CEST) ::Czekaj! Jeszcze jedno miałem powiedzieć Jeśli są problemy i nadchodzące wkłady, może za jakiś czas może wrócę do Kaszubskiej Wikipedii. Ale teraz już będę, więc to nie będzie pożegnanie. [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 17:30, 5 lëp 2026 (CEST) 9s64qf0rw8dbo8qbg4wxsqy5l86r5dy Òstrowë Salomóna 0 12399 204509 201370 2026-07-06T11:52:19Z Iketsi 3254 {{jãz.|xx}} 204509 wikitext text/x-wiki {{Infobox-państwò|gwôsné miono=Solomon Islands|miono=Òstrowë Salomóna|fana=Flag of the Solomon Islands.svg|herb=Coat of arms of the Solomon Islands.svg|miono-genitiw=Òstrowów Salomóna|mòtto=To Lead is to Serve (Prowadzëc znôczô służëc)|na karce=Solomon Islands on the globe (Oceania centered).svg|jãzëk=[[anielsczi jãzëk|anielsczi]]|stolëca=Honiara|fòrma państwa=Kònstitucëjnô mònarchijô|wiéchrzëzna=28 450|rok=2025|lëdztwò=838,645|dëtk=dolar Òstrowów Salomóna|kòd dëtka=SBD|czasowô cona=+11|swiãto=[[7 lëpińca]]|himn=God Save Our Solomon Islands|kòd=SB|Internet=.sb|telefón=677|Premiéra=Jeremiah Manele|Prezydeńt=David Tiva Kapu|Mònarcha=Karól III|data ùsôdzenia=7 lëpińca 1978|aùtowi kòd=SOL}} '''Òstrowë Salomóna''' ({{jãz.|pl}} ''Solomon Islands'') sã państwã na òstrowach w [[Òceaniô|Oceanije]], w Melanezji, nad [[Spòkójny Òcean|Spòkójnym Òceanã]]. Bëłë kòlonią [[Wiôlgô Britanijô|Wiôldżi Britanji]], zwëskałë samòstójnotã 7 lëpińca 1978<ref>[https://www.spc.int/events/solomon-islands-independence-day-2025 Solomon Islands independence day 2025]</ref>. == Geògrafiô == * Pòłożenié: Krôj leżë na òstrowach na pôłniowo-zôpadnym [[Spòkójny Òcean|Spòkójnym Òceanã]], 2000 km òd nordowo-pòrënkowi [[Aùstralëjô|Aùstralëji]]. Państwò cignje sã na òk. 1000 òstrowów (w tim 9 przédnich), stolëca (Honiara) leżë na nôwikszim òstrowie Guadalcanal<ref name=":0">[https://www.dfat.gov.au/geo/solomon-islands/solomon-islands-country-brief Solomon Islands country brief] Australian Government</ref> * Wiéchrzëzna: ** całownô: 28,896 ** wiéchrzëzna lądu: 27,986 ** wiéchrzëzna wòdów: 910 * Brzegòwô liniô: 5,313 * Klimat: tropikalny mónsunowi, niewiele skrajnëch cepłot ë pògòdowëch warënków * Ùsztôłcenié terenu: przédno sërowe górë, z czilema nisczimi kòralowëmã atolamã * Wësokòsc terenu: ** nôniższô wësokòsc: 0 (Spòkójny Òcean) ** nôwëższi czëp: 2,335 (czëp Popomanaseu) == Demografiô == * Lëdztwò: 838,645 (2025)<ref>[https://www.worldometers.info/world-population/solomon-islands-population/ Solomon Islands Population <small>(LIVE)</small>] Worldometer</ref> * Etniczné karna: W wikszosci Melanezijczicë (93%), w mniejszosci Polinezijczicë (4%), Micronezijczicë (1.5%), Chińczicë, Europejczicë ë jinszi (1.5%)<ref name=":1">[https://www.un.int/solomonislands/solomonislands/country-facts Country Facts | Solomon Islands] The Permanent Mission of Solomon Islands to the United Nations</ref>. * Jãzëczi: Òficjalny je anielsczi jãzek, le je też 63 jinszich<ref name=":0" />. * Religijné karna: nôwikszą religią je [[Chrzescëjanizna|Chrzescejanizna]] (95%), w tim Kòscół Melanezji (anglikańsczi) 29%, [[Katolëcczi Kòscół]] 19%, Ewangelëcczi Kòscół Pôłniowego Mòrza 17%, Metodëscë 11%, Adwentiscë Sódmégo Dnia 10%<ref name=":1" /> * Ùrbanizacjô: Wikszosc mieszkańców żëje na wiesczi terenach, òk. 73.5%<ref>[https://www.theglobaleconomy.com/Solomon-Islands/population_size/ Solomon Islands: Population size] Global Economy</ref> == Pòliticzny system == * fòrma państwa: kònstitucëjnô [[mònarchiô]] * Wëkònôwczô władza: król (Karól III<ref>[https://solomons.gov.sb/government/ About the Solomon Islands Government] Solomon Islands Government</ref>), reprezentowany przez gùbernatóra (David Tiva Kapu<ref>[https://solomons.gov.sb/governor-general-leaves-for-knighthood-in-london/ Governor General leaves for knighthood in London] Solomon Islands Goverment</ref>) ë premiera (Jeremiah Manele<ref>[https://forumsec.org/forum-leaders/hon-jeremiah-manele The Hon Jeremiah Manele] Pacific Islands Forum</ref>) * Ùstawòdôwczô władza: jednojizbowi parlament, 50 nôleżników wëbieranëch na sztërëlatną kadencjã<ref>[https://parliament.gov.sb/about/parliament/ About] National Parliament of Solomon Islands</ref>. == Òbaczë téż == * [[Aùstralëjô (kòntinent)]] == Przëpisë == {{Przëpisë}} == Bibliografiô == * [https://www.cia.gov/the-world-factbook/countries/solomon-islands/|The World Factbook] == Bùtnowé lënczi == {{Commons}} {{Aùstralëjô i Òceaniô}} {{Kòntrola}} [[Kategòrëjô:Państwa w Aùstralëji i Oceanije]] cmf3f8tv98ucnl6cxmzxiynzzf6fob5 Diskùsëjô brëkòwnika:Ясамойла 3 12915 204496 200107 2026-07-06T11:10:39Z Orbitminis 18250 /* */ Odpowiedź 204496 wikitext text/x-wiki Witóm serdeczno w '''kaszëbsczi Wikipedie''', pónie Ясамойла! [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 17:43, 12 môj 2026 (CEST) :Дзякуй. [[Brëkòwnik:Ясамойла|Ясамойла]] ([[Diskùsëjô brëkòwnika:Ясамойла|diskùsëjô]]) 17:46, 12 môj 2026 (CEST) ::(Przepraszam, że piszę po polsku, bo nie umiem pisać po białorusku!) Nie ma sprawy, panie Ясамойла! Dziękuję za stworzenie szablonów Wieży Babel i wznoszenie wkładów do kaszubskiej Wikipedii! Ponieważ mi się cieszy. Więc postanowiłem to powiedzieć o tych powodach. Dlaczego chciałem się powitać Pana po kaszubsku? W kaszubskiej Wikipedii nie posiadamy przynajmniej szablonu do powitania użytkowników po kaszubsku, ponieważ administratorzy kaszubskiej Wikipedii Tsca, Kirsan i Kaszeba są zajęci lub emerytowani od Wikipedii, a kolejnym: Dlaczego tworzysz szablony Wieży Babel? Ponieważ w kaszubskiej Wikipedii jest obecnie za mało szablonów Babel z powodu nieaktywnych użytkowników, bo nie chcą poświęcić dużo czasu na tworzenie szablonów i wkładów. Jeśli tekst jest niezrozumiały lub napisany w obcym języku, użyj tłumacza oraz rozszyfruj tekst. Dziękuję za uwagę. PS. Ja też stworzyłem szablon Wieży Babel, ale do języka rosyjskiego. Pozdrawiam, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 19:04, 12 môj 2026 (CEST) :Добра. [[Brëkòwnik:Ясамойла|Ясамойла]] ([[Diskùsëjô brëkòwnika:Ясамойла|diskùsëjô]]) 17:20, 20 môj 2026 (CEST) ::I oczywiście nie wiedziałem, że w cerkiewnosłowiańskiej Wikipedii (https://cu.wikipedia.org/wiki/%D0%93%D0%BB%D0%B0%D0%B2%D1%8C%D0%BD%D0%B0_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0) jesteś jeden z największych edytorów tej małej encyklopedii i deklarujesz języka cerkiewnosłowiańskiego (''Словѣньскъ ѩꙁꙑкъ'') jako ojczysty język ze względu, że chodzisz do cerkwi codziennie lub zapamiętałeś cały wschodniosłowiański język liturgiczny na pamięć :0 ::Przecież język cerkiewnosłowiański już wymarł setki lat temu i nie posiadał żaden ojczystych użytkowników, czyli, tak czy siak, to mogło być błąd, bo [[Internet]] nas oszukał. ::ꙁ​ ​богѣмъ​ ☦️🙏. [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 13:10, 6 lëp 2026 (CEST) ib9s6gpljk5jrhdidqjwfv69j8znzzb 204497 204496 2026-07-06T11:12:42Z Orbitminis 18250 204497 wikitext text/x-wiki Witóm serdeczno w '''kaszëbsczi Wikipedie''', pónie Ясамойла! [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 17:43, 12 môj 2026 (CEST) :Дзякуй. [[Brëkòwnik:Ясамойла|Ясамойла]] ([[Diskùsëjô brëkòwnika:Ясамойла|diskùsëjô]]) 17:46, 12 môj 2026 (CEST) ::(Przepraszam, że piszę po polsku, bo nie umiem pisać po białorusku!) Nie ma sprawy, panie Ясамойла! Dziękuję za stworzenie szablonów Wieży Babel i wznoszenie wkładów do kaszubskiej Wikipedii! Ponieważ mi się cieszy. Więc postanowiłem to powiedzieć o tych powodach. Dlaczego chciałem się powitać Pana po kaszubsku? W kaszubskiej Wikipedii nie posiadamy przynajmniej szablonu do powitania użytkowników po kaszubsku, ponieważ administratorzy kaszubskiej Wikipedii Tsca, Kirsan i Kaszeba są zajęci lub emerytowani od Wikipedii, a kolejnym: Dlaczego tworzysz szablony Wieży Babel? Ponieważ w kaszubskiej Wikipedii jest obecnie za mało szablonów Babel z powodu nieaktywnych użytkowników, bo nie chcą poświęcić dużo czasu na tworzenie szablonów i wkładów. Jeśli tekst jest niezrozumiały lub napisany w obcym języku, użyj tłumacza oraz rozszyfruj tekst. Dziękuję za uwagę. PS. Ja też stworzyłem szablon Wieży Babel, ale do języka rosyjskiego. Pozdrawiam, [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 19:04, 12 môj 2026 (CEST) :Добра. [[Brëkòwnik:Ясамойла|Ясамойла]] ([[Diskùsëjô brëkòwnika:Ясамойла|diskùsëjô]]) 17:20, 20 môj 2026 (CEST) ::I oczywiście nie wiedziałem, że w cerkiewnosłowiańskiej Wikipedii (https://cu.wikipedia.org/wiki/%D0%93%D0%BB%D0%B0%D0%B2%D1%8C%D0%BD%D0%B0_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0) jesteś jeden z największych edytorów tej małej encyklopedii i deklarujesz języka cerkiewnosłowiańskiego (''Словѣньскъ ѩꙁꙑкъ'') jako ojczysty język ze względu, że chodzisz do cerkwi codziennie lub zapamiętałeś cały wschodniosłowiański język liturgiczny na pamięć :0 ::Przecież język cerkiewnosłowiański już wymarł setki lat temu i nie posiadał żaden ojczystych użytkowników, czyli, tak czy siak, to mogło być błąd, bo [[Internet]] nas oszukał. ::ꙁ​ ​богѣмъ​ ☦️🙏 : [[Brëkòwnik:Orbitminis|Orbitminis]] ([[Diskùsëjô brëkòwnika:Orbitminis|diskùsëjô]]) 13:10, 6 lëp 2026 (CEST) strax5zaj6k5i419zol15skokb9jm8w Brëkòwnik:Rabùszniczka/zapisownik 2 13080 204469 203359 2026-07-05T15:48:55Z Rabùszniczka 19710 204469 wikitext text/x-wiki == Je to mój zapisownik, proszã mie tuwò nie zazérac i nick nie pisac! == == Òlga Bòznańskô == >tuwò mdzë infòbòks jak Michôł zrobi< Òlga Helena Karolëna Bòznańskô (ùr. 15 łżëkwiata 1865 w Krakòwie, zm. 26 rujana 1940 w Pariżu) - pòlskô malarkô, pòrtecëstka epòczi mòdernizmù; nôleżniczka mònachijsczi szkòłë, Karna Pòlsczich Artistów "Sztuka" (1898) i Société Nationale des Beaux-Arts (1901). == Kania == '''Kanie''' abò '''kùlindżi''' (''Milvus'') – pòzwa na ôrt drapiérzëch ptôchów z rodzëznë [[Accipitridae|jastrzibòwatëch]] (Accipitridae), jakô w kaszëbsczim jãzekù òdnosy sã do rodzajów [[Milvus]] (do jaczégò nôleżą [[lësatô kania]], [[czôrnô kania]], episkô kania), ''[[Haliastur]], [[Ictinia]] (''kanialuczi'').'' === Kanie w Kaszëbsczi kùlturze. === >tuwò napiszã ò pòzdrzatkù na kanie ù Zëchtë i ò scynaniu kanie< == Lësatô Kania == '''Lësatô kania''' (''Milvus milvus'') – gatënk drapieżnégò ptôcha z rodzëznë [[Accipitridae|Jastrzibòwatëch]]. Gatënk ten dzysdnia gniôzdëje w Eùropie, chòc w dôwnoce rozmnażôł sã téż w zôpadny Azji i północno-zôpadny Africe<ref>BirdLife International (2020). "Milvus milvus". IUCN Red List of Threatened Species. 2020 e.T22695072A181651010. doi:10.2305/IUCN.UK.2020-3.RLTS.T22695072A181651010.en. Retrieved 19 November 2021.</ref>. === Systematika === Lësatô kania òstała òpisónô przez Szwedzczégò rôdnika [[Karol Linneùsz|Karla Linneùsza]] w 1758 w dzesąti edycje jegò dokôzu ''Systema Naturae'' pòd binominalną gatënkòwą pòzwą ''Falco milvus''<ref>[[Karol Linneùsz|Linneùsz, Karol]] (1758) [https://www.biodiversitylibrary.org/page/726996#page/106/mode/1up Systema naturae per regna tria naturae, secundum classes, ordines, genera, species, cum characteribus, differentiis, synonymis, locis. Tomus I. Editio decima, reformata] (w łacëznie), Tom pierszi, Vol. 1. Stockholm: Laurentius Salvius. p. 89. "F. cera flava, cauda forsicata, corpore ferrugineo, capite albidiore."</ref>. Słowò ''Milvus'' je łaceńską pòzwą na tegò ptôcha. W 1799 francëzczi rodnik Bernard Germain de Lacépède przeniósł gatënk do rodz''aju Milvus.'' Wëapartniwóné są dwa pòdgatënczi Milvus milvus * ''M. milvus milvus'' <small>(Linnaeus, 1758)</small> – '''lësatô kania''' * ''M. milvus fasciicauda'' <small>E. Hartert, 1914</small> – '''zelonogłownô kania''' – endemit Òstrowów Zelony Głowë, prawie na gwës wymarłi W rodzaju ''Milvus'' są téż dwa jinszé gatënczi: czôrnô kania i egipskô kania. Znóné są przëtrôfczi ùdany hybridizacji midzë lësatima i czôrnyma kaniami. === Hybridizacjô === Rodzôj ''Milvus'' zawierô w so jesz dwa jinszé gatënczi - Czôrną kaniã i Egipską kaniã<ref>Gill, Frank; Donsker, David, eds. (2018). [http://www.worldbirdnames.org/bow/raptors/ "New World vultures, Secretarybird, kites, hawks, eagles]". World Bird List Version 8.2. International Ornithologists' Union. Retrieved 24 September 2018.</ref>. Są znóné przëtrôfczi w jaczich lësatim kanióm ùdało sã rozmnożëc z czôrnyma kaniamim, dzało sã to w niewòli jak i dzëczim stónie jak np. na Òstrowach Zelony Głowë. Strefa hybridizacje w jaczi òba gatënczi òkazyjnie sã krziżują jistnieje w Centralny Eùropie.<ref>Heneberg, Petr; Dolinay, Matej; Matuší, Hynek; Pfeiffer, Thomas; Nachtigall, Winfried; Bizos, Jiri; Šimčiková, Daniela; Literák, Ivan (2016). [https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0159202 "Conservation of the Red Kite Milvus milvus (Aves: Accipitriformes) Is Not Affected by the Establishment of a Broad Hybrid Zone with the Black Kite Milvus migrans migrans in Central Europe"]. PLoS ONE. 11 (7). Retrieved 30 August 2025.</ref> Geneticzné badérowaniô kaniów z ti rëmi nie wëkazałe niżódnégò [[mtDNA]] czôrnëch kaniów w lësatich kaniach i vice versa. Przez to, że ten ôrt DNA je przekazëwóny przez nënczi, aùtorzë badaniô sùgerują, że mieszóné samice są bezpłodné w zgòdze z [[Prawò Haldana|prawã Haldana]]. W 2006 rokù w Szkòcji doszło do ùdanégò rozrodu samca czôrny kanie z samicą lësowati kanie w jaczim ùdało jim sã wëchòwac dwa pòdlotë. === Charakteristika === Ptôch mô wësmùkłą sylwetkã i pstrokaté pióra. Jegò cało mô 60 - 72 cm długòscy<ref>Orta, J., Christie, D.A. & Garcia, E.F.J.: [https://www.hbw.com/species/red-kite-milvus-milvus Red Kite (Milvus milvus)]. [w:] del Hoyo, J., Elliott, A., Sargatal, J., Christie, D.A. & de Juana, E. (eds.). Handbook of the Birds of the World Alive.</ref>, i 175 - 195 cm w rozpiertoscy lecëdłów. Samce ważą òd 800 do 1200 gramów, timczasã samice są wiãkszé ważą midzë 1000, a 1300 gramów. Pòza różnicą wôdze midzë płcama ptôcha ni ma wiãkszich różniców - òbie płce są szlachòwno ùbarwióné - mają szarawą głowã, przeważnie rdzawé cało z cemniejszima plecama i białima akcentama na lëcadłach. W przerównanim do czôrny kani, lësatô kania je kąsk wiãkszô i ma charakteristiczny mòcno wcãti ògón (niżóden drapieżnik Eùropë mô jaż tak mòcné wcãce jak lësatô kania). W pòrównaniu z mëszołowã je wiãkszô i mô dłuższé lëcadła. === Wëstãpòwanié: === Lësaté kanie są ptôchama jaczé zamieszkùją lëstowaté lasë, łączi, pòla dolenë i sampòwaté pògreńcza. Pòchòdzą z zôpadny Palearktyczny krajinë, tak tej wszëtczé znóné rodné parë (jaczich je kòl 32,200 - 37,000) gniôzdëją w Eùropie. Przede wszëtczim żëje w zôpadny, strzôdkòwi i pôłniowi Eùropie, nielëcznie téż w wschòdnym parce Azji Mniszi i w północny Africe, jaczi status nie je gwësny - lãgòwô pòpùlacja z Maroka je ùznôwónô za wëmarłą, chòc sómé kanie dali zëmùje w tim kraju. Dawni gatënk mòżna bëło pòtkac téż na Òstrowach Kanarijsczich i Òstrowach Zelonégò Przëlądka(?) gdze prawie na gwës wëmôrł<ref>Garcia-del-Rey, E. (2011). Field Guide to the Birds of Macaronesia. Azores, Madeira, Canary Islands, Cape Verde. Lynx Edicions, Barcelona, Spain.</ref>. Kania je wãdrownô, chòc to zanôlégô òd ji pòpùlacji - ptôchë z pôłni mieszkają ù se całi rok, a te żëjącé w ùmiarkòwónym klimace migrëją òd zélnikã do séwnikã nad Strzôdzemné Mòrze, abë wrócëc do swòjich gniôzd kòl strëmiannikã. W przërównanim do [[Czôrnô kania|czôrny kanie]] ji zasyg je barżi ògraniczony. f1bkqegdgzzzcg6ye20q3pbavrozu50 Brëkòwnik:Terrus38 2 13091 204443 204368 2026-07-05T13:08:36Z Jakkonles12 8987 204443 wikitext text/x-wiki == Jô == {{Babel|pl|csb-4|en-3|lv-3|sr-3|de-2|ru-1}}Pòmòrzón, lubòtnik jãzëków, rézów, jazdë na kòle, pòliticzi a wiele jinëch. M.jin. sztudéra kaszëbsczi etnofilologie, w 2025 r. dobiwca Kaszëbsczégò Diktanda. Pòzdrówk, '''make csbwiki great''' again (nawetka nié great again, prosto great)! == Wkłôd == Nôwikszi wkłôd miôł jem w ne starnë: [[Ùskô]], [[Brainstorm (mùzyczné karno)]], [[Benedikt XVI]], [[Szablóna:Mùzyczné karno infòbòks]], ale dokłôdóm pò përznie do wiele wicy artiklów, òsoblëwie zajimóm sã kòrektą. == Jiné == {{Brëkòkôstka|[[Òbrôzk:White-blue-white flag.svg|40px|border]]|{{gender:{{BASEPAGENAME}}|Ten Brëkòwnik|Ta brëkòwniczka|Ten brëkòwnik}} popiérô biôtkã ò [[Demòkracjô|demòkracjã]] w [[Ruskô|Rusce]].<br />Свобода России!|tło1=blue|tło2=white}}{{Brëkòkôstka|[[Òbrôzk:Flag_of_Belarus_(1918,_1991-1995).svg|40px|border]]|{{gender:{{BASEPAGENAME}}|Ten brëkòwnik|Ta brëkòwniczka|Ten brëkòwnik}} popiérô biôtkã ò [[Demòkracjô|demòkracjã]] na [[Biôłorus|Biôłorusë]].<br />Жыве Беларусь!|tło1=red|tło2=white}}{{Brëkòkôstka|[[Òbrôzk:P human body.svg|40px]]|{{gender:{{BASEPAGENAME}}|Ten brëkòwnik mô sã za '''[[Hùmanistika|humanistã]]'''.|Ta brëkòwniczka mô sã za '''[[Hùmanistika|humanistkã]]'''.}}|tło1=white|tło2=#D8BFD8|ramka=black}}{{Brëkòkôstka|[[Òbrôzk:Palestine Waving Flag Icon.svg|48px|Palestine Waving Flag Icon]]|{{gender:{{BASEPAGENAME}}|Ten wikipedista|Ta wikipedistka}} <b>pòpiérô</b> biôtkã palestińsczégò nôrodu ë sprocëmniwô sã lëdobójstwù w Conie Gazë|tło1=#009000|tło2=#008000|kolor2=white}}{{Brëkòkôstka|[[Òbrôzk:Bicycle-icon.png]]|{{gender:{{BASEPAGENAME}}|Ten brëkòwnik|Ta brëkòwniczka}} rôd [[rekreacjô|rekreacjowò]] jezdzy na [[Kòło|kòle]].|tło1=white|tło2=lightgrey}}{{Brëkòkôstka|tło1=#FEAAAA|tło2=white|[[Òbrôzk:Anticapitalismo.svg|45px]]|Ten brëkòwnik jest procëmny wprowadzeniu [[eùro]]. Je òn dbë, że mòże pò wprowadzeniu ti walutë swiat sã nie zawôli, le równak Polskô stracy cësk na mònetarną pòlitikã w gwôsnym kraju.}}{{Brëkòkôstka|{{NUMBEROFARTICLES}}|Nen brëkòwnik mëszli, że kaszëbskô Wikipediô mô za mało artiklów.|tło1=#7E7CF2|tło2=#4CEB3F}}{{Brëkòkôstka | ramka = Black | tło2 = #CC0000 | tło1 = White | farwa2 = White | 1 = [[Òbrôzk:Grammar Nazi Icon Text.svg|80px]] | 2 = Ten brëkòwnik je òrtograficznym faszistą. }} dv9i9d6qb9t3h27emjzrmfy3kntblce 204444 204443 2026-07-05T13:26:10Z Terrus38 14026 ej ale jem òrtograficznym faszistą, nie jem bezznankòwnikòwim faszistą, ok? 204444 wikitext text/x-wiki == Jô == {{Babel|pl|csb-4|en-3|lv-3|sr-3|de-2|ru-1}}Pòmòrzón, lubòtnik jãzëków, rézów, jazdë na kòle, pòliticzi a wiele jinëch. M.jin. sztudéra kaszëbsczi etnofilologie, w 2025 r. dobiwca Kaszëbsczégò Diktanda. Pòzdrówk, '''make csbwiki great''' again (nawetka nié great again, prosto great)! == Wkłôd == Nôwikszi wkłôd miôł jem w ne starnë: [[Ùskô]], [[Brainstorm (mùzyczné karno)]], [[Benedikt XVI]], [[Szablóna:Mùzyczné karno infòbòks]], ale dokłôdóm pò përznie do wiele wicy artiklów, òsoblëwie zajimóm sã kòrektą. == Jiné == {{Brëkòkôstka|[[Òbrôzk:White-blue-white flag.svg|40px|border]]|{{gender:{{BASEPAGENAME}}|Ten Brëkòwnik|Ta brëkòwniczka|Ten brëkòwnik}} popiérô biôtkã ò [[Demòkracjô|demòkracjã]] w [[Ruskô|Rusce]].<br />Свобода России!|tło1=blue|tło2=white}}{{Brëkòkôstka|[[Òbrôzk:Flag_of_Belarus_(1918,_1991-1995).svg|40px|border]]|{{gender:{{BASEPAGENAME}}|Ten brëkòwnik|Ta brëkòwniczka|Ten brëkòwnik}} popiérô biôtkã ò [[Demòkracjô|demòkracjã]] na [[Biôłorus|Biôłorusë]].<br />Жыве Беларусь!|tło1=red|tło2=white}}{{Brëkòkôstka|[[Òbrôzk:P human body.svg|40px]]|{{gender:{{BASEPAGENAME}}|Ten brëkòwnik mô sã za '''[[Hùmanistika|humanistã]]'''.|Ta brëkòwniczka mô sã za '''[[Hùmanistika|humanistkã]]'''.}}|tło1=white|tło2=#D8BFD8|ramka=black}}{{Brëkòkôstka|[[Òbrôzk:Palestine Waving Flag Icon.svg|48px|Palestine Waving Flag Icon]]|{{gender:{{BASEPAGENAME}}|Ten wikipedista|Ta wikipedistka}} <b>pòpiérô</b> biôtkã palestińsczégò nôrodu ë sprocëmniwô sã lëdobójstwù w Conie Gazë|tło1=#009000|tło2=#008000|kolor2=white}}{{Brëkòkôstka|[[Òbrôzk:Bicycle-icon.png]]|{{gender:{{BASEPAGENAME}}|Ten brëkòwnik|Ta brëkòwniczka}} rôd [[rekreacjô|rekreacjowò]] jezdzy na [[Kòło|kòle]].|tło1=white|tło2=lightgrey}}{{Brëkòkôstka|tło1=#FEAAAA|tło2=white|[[Òbrôzk:Anticapitalismo.svg|45px]]|Ten brëkòwnik jest procëmny wprowadzeniu [[eùro]]. Je òn dbë, że mòże pò wprowadzeniu ti walutë swiat sã nie zawôli, le równak Polskô stracy cësk na mònetarną pòlitikã w gwôsnym kraju.}}{{Brëkòkôstka|{{NUMBEROFARTICLES}}|Nen brëkòwnik mëszli, że kaszëbskô Wikipediô mô za mało artiklów.|tło1=#7E7CF2|tło2=#4CEB3F}}{{Brëkòkôstka | ramka = Black | tło2 = #CC0000 | tło1 = White | farwa2 = White | 1 = [[Òbrôzk:Grammar Nazi Icon Text.svg|80px]] | 2 = Ten brëkòwnik je '''òrtograficznym''' faszistą. }} jiju9327e685i74cx2tzw84870lk4ev Mésterstwò Swiata w Fùsbalë 2026 0 13158 204447 204434 2026-07-05T14:10:50Z HaxelKaszëba 17492 ùprawkô 204447 wikitext text/x-wiki {{Spòrtowi Turniér infobox|gòspòdôrzë=[[Òbrôzk:Flag of Canada.svg|tło|22x22px]] [[Kanada]] [[Òbrôzk:Flag of Mexico.svg|tło|22x22px]] [[Meksyk]] [[Òbrôzk:Flag of the United States.svg|tło|22x22px]] [[Zjednóné Kraje Americzi|Zjednóné Krôje Americzi]]|data=11 czerwińca 2026|pòzwa=Mésterstwò Swiata w fùsbalë 2026<br>FIFA World Cup 2026<br>Coupe du monde de football de 2026<br>Copa Mundial de Fútbol 2026|òbrôzk=2026 FIFA World Cup emblem (with wordmark).svg|reprezentacje=48|stadiónë=16|mecze=90|gòle= 261|ùszłô=[[Mésterstwò Swiata w fùsbalë 2022|2022]]|aktualné=2026]}} '''XXIII Mésterstwò Swiata w fùsbalë 2026''' – 23. edicjô mésterstwô swiata twòrzoné przez [[FIFA]]. Turniér òdbëwô sã òd [[11 czerwińca]] do [[19 lëpińca]] 2026 rokù na stadionach w [[Kanada|Kanadze]], [[Meksyk|Meksykù]] a [[Zjednóné Kraje Americzi|Zjednónëch Krôjach]]<ref>FIFA: ''[https://www.fifa.com/en/tournaments/mens/worldcup/canadamexicousa2026 FIFA World Cup 2026 Official Website]''. [przistãp 2026-06-29].</ref>. Je to pierszi mùndial twòrzony wespólnie przez trzë krôje òrôz pierszi w historie z dzélã 48 nôrodnëch reprezentacjów. Nowi fòrmat turniéru òbjimùje 12 grëp pò szterëch karnów, z jaczich do pùcharowi fazë awansëje dwie nôlepszé reprezentacje z kòżdi grëpë òrôz òsmë nôlepszich karnów z trzecëch placów. Òznôczô to rozegranié rekordowi lëczbë 104 zeńdzeniów<ref>FIFA: ''[https://www.fifa.com/en/tournaments/mens/worldcup/canadamexicousa2026/articles/fifa-council-approves-international-match-calendar-new-format FIFA Council approves international match calendar and 104-match World Cup format]''. [przistãp 2026-06-29].</ref>. == Welënk gòspòdôrzów == [[Òbrôzk:2026 world cup.svg|mały|Wëniczi w òsoblëwëch krôjach]] 13 czerwińca 2018 rokù òb czas Kòngresu FIFA w Mòskwie dobéła wespólna kandidatura Kanadë, Meksykù a Zjednónëch Krôjów, przedobiwôjącë bédënk [[Marokò|Maroka]]<ref>FIFA: ''[https://www.fifa.com/en/tournaments/mens/worldcup/canadamexicousa2026/articles/united-2026-bid-wins-hosts-fifa-world-cup United 2026 bid voted hosts of the 2026 FIFA World Cup]''. [przistãp 2026-06-29].</ref>. Turniér je historiczny z czilu pòwodów: * pierszi mundial twòrzony przez trzë krôje; * Meksyk òstôł pierszim krôjã, jaczi gòscy mésterstwò swiata pò rôz trzecy; * Kanada pò rôz pierszi twòrzy chłopsczi mésterstwò swiata; * Zjednóné Krôje twòrzą mundial pò rôz drëdżi (pò 1994 rokù). Hewò wëniczi welowaniô: {| class="wikitable" style="text-align: center;" !Państwò !Głosë |- | align="left" |[[Òbrôzk:Flag of Canada.svg|tło|22x22px]] [[Kanada]] [[Òbrôzk:Flag of Mexico.svg|tło|22x22px]] [[Meksyk]] [[Òbrôzk:Flag of the United States.svg|tło|22x22px]] [[Zjednóné Kraje Americzi|Zjednóné Krôje Americzi]] |'''134''' |- | align="left" | [[Òbrôzk:Flag of Morocco.svg|tło|22x22px]] [[Marokò]] |65 |} == Fòrmat rozgriwków == Pò rôz pierszi w historie w finałach wëstãpùje 48 reprezentacjów. [[Òbrôzk:FIFA World Cup 2026 Draw Reception (54972845582).jpg|mały|Òficjalnô bala na mésterstwô, Trionda]] Fòrmat òbjimùje: * 12 grëp pò 4 karnów, * Kòżdé karno rozgriwô 3 grëpowé mecze, * awans zwëskiwają: ** dobiwcë grëp, ** karnë z drëdżich placów, ** òsmë nôlepszich reprezentacjów z trzecych placów, * òd pùcharowi fazë òbrzeszëwùje pùcharowi system rozpòczinôjącë sã òd 1/16 finału (Round of 32). == Gardë gòspòdôrzów == Turniér òdbëwô sã na 16 stadionach<ref>FIFA: ''[https://www.fifa.com/en/tournaments/mens/worldcup/canadamexicousa2026/articles/fifa-unveils-stellar-line-up-of-host-cities-for-2026-world-cup FIFA unveils stellar line-up of host cities for 2026 FIFA World Cup]''. [przistãp 2026-06-29].</ref>. === [[Kanada]] === * Toronto * Vancouver === [[Meksyk]] === * Meksyk * Guadalajara * Monterrey === [[Zjednóné Kraje Americzi|Zjednóné Krôje]] === * Atlanta * Boston * Dallas * Houston * Kansas City * Los Angeles * Miami * [[Nowi Jork]] / East Rutherford * Filadelfia * San Francisco Bay Area (Santa Clara) * Seattle == Grëpòwa faza == Reprezentacje òstôłë pòdzelóné na 12 grëp òznôczonëch lëterama òd A do L. Kòżdé karno rozgriwô trzë zeńdzeniô. === Grëpa A === {{#invoke:Grëpa MS|render |group=Grëpa A |1=[[File:Flag of Mexico.svg|20px]] Meksyk |1pkt=9 |1m=3 |1w=3 |1r=0 |1p=0 |1g+=6 |1g-=0 |1rg=+6 |2=[[File:Flag of South Africa.svg|20px]] Pôłniowô Afrika |2pkt=4 |2m=3 |2w=1 |2r=1 |2p=1 |2g+=2 |2g-=3 |2rg=-1 |3=[[File:Flag of South Korea.svg|20px]] Pôłniowô Korea |3pkt=3 |3m=3 |3w=1 |3r=0 |3p=2 |3g+=2 |3g-=3 |3rg=-1 |4=[[File:Flag of Czech Republic.svg|20px]] Czeskô |4pkt=1 |4m=3 |4w=0 |4r=1 |4p=2 |4g+=2 |4g-=4 |4rg=-2 }} === Grëpa B === {{#invoke:Grëpa MS|render |group=Grëpa B |advance=3 |1=[[File:Flag of Switzerland.svg|20px]] Szwajcarskô |1pkt=7 |1m=3 |1w=2 |1r=1 |1p=0 |1g+=6 |1g-=3 |1rg=+3 |2=[[File:Flag of Canada.svg|20px]] Kanada |2pkt=4 |2m=3 |2w=1 |2r=1 |2p=1 |2g+=5 |2g-=4 |2rg=+1 |3=[[File:Flag of Bosnia and Herzegovina.svg|20px]] Bòsniô a Hercegòwina |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=4 |3g-=5 |3rg=-1 |4=[[File:Flag of Qatar.svg|20px]] Katar |4pkt=1 |4m=3 |4w=0 |4r=1 |4p=2 |4g+=2 |4g-=9 |4rg=-7 }} === Grëpa C === {{#invoke:Grëpa MS|render |group=Grëpa C |1=[[File:Flag of Brazil.svg|20px]] Brazylskô |1pkt=7 |1m=3 |1w=2 |1r=1 |1p=0 |1g+=7 |1g-=1 |1rg=+6 |2=[[File:Flag of Morocco.svg|20px]] Marokò |2pkt=7 |2m=3 |2w=2 |2r=1 |2p=0 |2g+=6 |2g-=3 |2rg=+3 |3=[[File:Flag of Scotland.svg|20px]] Szkòckô |3pkt=3 |3m=3 |3w=1 |3r=0 |3p=2 |3g+=2 |3g-=5 |3rg=-3 |4=[[File:Flag of Haiti.svg|20px]] Haiti |4pkt=0 |4m=3 |4w=0 |4r=0 |4p=3 |4g+=2 |4g-=8 |4rg=-6 }} === Grëpa D === {{#invoke:Grëpa MS|render |group=Grëpa D |advance=3 |1=[[File:Flag of United States.svg|20px]] USA |1pkt=6 |1m=3 |1w=2 |1r=0 |1p=1 |1g+=8 |1g-=4 |1rg=+4 |2=[[File:Flag of Australia.svg|20px]] Aùstraliô |2pkt=4 |2m=3 |2w=1 |2r=1 |2p=1 |2g+=2 |2g-=2 |2rg=0 |3=[[File:Flag of Paraguay.svg|20px]] Paragwaj |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=3 |3g-=4 |3rg=-1 |4=[[File:Flag of Turkey.svg|20px]] Tëreckô |4pkt=3 |4m=3 |4w=1 |4r=0 |4p=2 |4g+=3 |4g-=5 |4rg=-2 }} === Grëpa E === {{#invoke:Grëpa MS|render |group=Grëpa E |advance=3 |1=[[File:Flag of Germany.svg|20px]] Niemieckô |1pkt=6 |1m=3 |1w=2 |1r=0 |1p=1 |1g+=10 |1g-=4 |1rg=+6 |2=[[File:Flag of Ivory Coast.svg|20px]] Ùbrzég Słoniowëch Kłów |2pkt=6 |2m=3 |2w=2 |2r=0 |2p=1 |2g+=4 |2g-=2 |2rg=+2 |3=[[File:Flag of Ecuador.svg|20px]] Ekwador |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=2 |3g-=2 |3rg=0 |4=[[File:Flag of Curaçao.svg|20px]] Curaçao |4pkt=1 |4m=3 |4w=0 |4r=1 |4p=2 |4g+=1 |4g-=9 |4rg=-8 }} === Grëpa F === {{#invoke:Grëpa MS|render |group=Grëpa F |advance=3 |1=[[File:Flag of Netherlands.svg|20px]] Niderlandzkô |1pkt=7 |1m=3 |1w=2 |1r=1 |1p=0 |1g+=10 |1g-=4 |1rg=+6 |2=[[File:Flag of Japan.svg|20px]] Japòńskô |2pkt=5 |2m=3 |2w=1 |2r=2 |2p=0 |2g+=7 |2g-=3 |2rg=+4 |3=[[File:Flag of Sweden.svg|20px]] Szwedzkô |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=7 |3g-=7 |3rg=0 |4=[[File:Flag of Tunisia.svg|20px]] Tunezjô |4pkt=0 |4m=3 |4w=0 |4r=0 |4p=3 |4g+=2 |4g-=12 |4rg=-10 }} === Grëpa G === {{#invoke:Grëpa MS|render |group=Grëpa G |1=[[File:Flag of Belgium.svg|20px]] Belgiô |1pkt=5 |1m=3 |1w=1 |1r=2 |1p=0 |1g+=6 |1g-=2 |1rg=+4 |2=[[File:Flag of Egypt.svg|20px]] Egipt |2pkt=5 |2m=3 |2w=1 |2r=2 |2p=0 |2g+=5 |2g-=3 |2rg=+2 |3=[[File:Flag of Iran.svg|20px]] Iran |3pkt=3 |3m=3 |3w=0 |3r=3 |3p=0 |3g+=3 |3g-=3 |3rg=0 |4=[[File:Flag of New Zealand.svg|20px]] Nowô Zelandzkô |4pkt=1 |4m=3 |4w=0 |4r=1 |4p=2 |4g+=4 |4g-=10 |4rg=-6 }} === Grëpa H === {{#invoke:Grëpa MS|render |group=Grëpa H |1=[[File:Flag of Spain.svg|20px]] Szpańskô |1pkt=7 |1m=3 |1w=2 |1r=1 |1p=0 |1g+=5 |1g-=0 |1rg=+5 |2=[[File:Flag of Cape Verde.svg|20px]] Cabo Verde |2pkt=3 |2m=3 |2w=0 |2r=3 |2p=0 |2g+=2 |2g-=2 |2rg=0 |3=[[File:Flag of Uruguay.svg|20px]] Ùrugwaj |3pkt=2 |3m=3 |3w=0 |3r=2 |3p=1 |3g+=3 |3g-=4 |3rg=-1 |4=[[File:Flag of Saudi Arabia.svg|20px]] Saùdijskô Arabiô |4pkt=2 |4m=3 |4w=0 |4r=2 |4p=1 |4g+=1 |4g-=5 |4rg=-4 }} === Grëpa I === {{#invoke:Grëpa MS|render |group=Grëpa I |advance=3 |1=[[File:Flag of France.svg|20px]] Francëskô |1pkt=9 |1m=3 |1w=3 |1r=0 |1p=0 |1g+=10 |1g-=2 |1rg=+8 |2=[[File:Flag of Norway.svg|20px]] Norweskô |2pkt=6 |2m=3 |2w=2 |2r=0 |2p=1 |2g+=8 |2g-=7 |2rg=+1 |3=[[File:Flag of Senegal.svg|20px]] Senegal |3pkt=3 |3m=3 |3w=1 |3r=0 |3p=2 |3g+=8 |3g-=6 |3rg=+2 |4=[[File:Flag of Iraq.svg|20px]] Irak |4pkt=0 |4m=3 |4w=0 |4r=0 |4p=3 |4g+=1 |4g-=12 |4rg=-11 }} === Grëpa J === {{#invoke:Grëpa MS|render |group=Grëpa J |advance=3 |1=[[File:Flag of Argentina.svg|20px]] Argentińskô |1pkt=9 |1m=3 |1w=3 |1r=0 |1p=0 |1g+=8 |1g-=1 |1rg=+7 |2=[[File:Flag of Austria.svg|20px]] Aùstriackô |2pkt=4 |2m=3 |2w=1 |2r=1 |2p=1 |2g+=7 |2g-=7 |2rg=0 |3=[[File:Flag of Algeria.svg|20px]] Algierskô |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=5 |3g-=7 |3rg=-2 |4=[[File:Flag of Jordan.svg|20px]] Jordańskô |4pkt=0 |4m=3 |4w=0 |4r=0 |4p=3 |4g+=3 |4g-=8 |4rg=-5 }} === Grëpa K === {{#invoke:Grëpa MS|render |group=Grëpa K |advance=3 |1=[[File:Flag of Colombia.svg|20px]] Kòlumbiô |1pkt=7 |1m=3 |1w=2 |1r=1 |1p=0 |1g+=4 |1g-=1 |1rg=+3 |2=[[File:Flag of Portugal.svg|20px]] Pòrtugalskô |2pkt=5 |2m=3 |2w=1 |2r=2 |2p=0 |2g+=6 |2g-=1 |2rg=+5 |3=[[File:Flag of Democratic Republic of the Congo.svg|20px]] DRK |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=4 |3g-=3 |3rg=+1 |4=[[File:Flag of Uzbekistan.svg|20px]] Ùzbekistan |4pkt=0 |4m=3 |4w=0 |4r=0 |4p=3 |4g+=2 |4g-=11 |4rg=-9 }} === Grëpa L === {{#invoke:Grëpa MS|render |group=Grëpa L |advance=3 |1=[[File:Flag of England.svg|20px]] Anielskô |1pkt=7 |1m=3 |1w=2 |1r=1 |1p=0 |1g+=6 |1g-=2 |1rg=+4 |2=[[File:Flag of Croatia.svg|20px]] Chòrwackô |2pkt=6 |2m=3 |2w=2 |2r=0 |2p=1 |2g+=5 |2g-=5 |2rg=0 |3=[[File:Flag of Ghana.svg|20px]] Ghana |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=2 |3g-=2 |3rg=0 |4=[[File:Flag of Panama.svg|20px]] Panama |4pkt=0 |4m=3 |4w=0 |4r=0 |4p=3 |4g+=0 |4g-=4 |4rg=-4 }} === Ranking karnów z trzecëch placów === {| class="wikitable" style="width:100%; table-layout:fixed; text-align:center; font-family:system-ui, Arial;" <tr style="background:#f8f9fa;"> <th style="width:6%;">M.</th> <th style="width:40%;">Reprezentacjô</th> <th style="width:15%;">Grëpa</th> <th style="width:9%;">Pkt</th> <th style="width:6%;">M</th> <th style="width:6%;">W</th> <th style="width:6%;">R</th> <th style="width:6%;">P</th> <th style="width:6%;">+</th> <th style="width:6%;">–</th> <th style="width:6%;">±</th> </tr> |- | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Democratic Republic of the Congo.svg|20px]] DRK | style="background:#dff6df; font-weight:bold;" | K | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | +1 |- | style="background:#dff6df; font-weight:bold;" | 2 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Ecuador.svg|20px]] Ekwador | style="background:#dff6df; font-weight:bold;" | E | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 2 | style="background:#dff6df; font-weight:bold;" | 2 | style="background:#dff6df; font-weight:bold;" | 0 |- | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Sweden.svg|20px]] Szwedzkô | style="background:#dff6df; font-weight:bold;" | F | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 7 | style="background:#dff6df; font-weight:bold;" | 7 | style="background:#dff6df; font-weight:bold;" | 0 |- | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Bosnia and Herzegovina.svg|20px]] Bòsniô a Hercegòwina | style="background:#dff6df; font-weight:bold;" | B | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 5 | style="background:#dff6df; font-weight:bold;" | −1 |- | style="background:#dff6df; font-weight:bold;" | 5 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Paraguay.svg|20px]] Paragwaj | style="background:#dff6df; font-weight:bold;" | D | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | −1 |- | style="background:#dff6df; font-weight:bold;" | 6 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Ghana.svg|20px]] Ghana | style="background:#dff6df; font-weight:bold;" | L | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 2 | style="background:#dff6df; font-weight:bold;" | 2 | style="background:#dff6df; font-weight:bold;" | 0 |- | style="background:#dff6df; font-weight:bold;" | 7 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Algeria.svg|20px]] Algierskô | style="background:#dff6df; font-weight:bold;" | J | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 5 | style="background:#dff6df; font-weight:bold;" | 7 | style="background:#dff6df; font-weight:bold;" | −2 |- | style="background:#dff6df; font-weight:bold;" | 8 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Senegal.svg|20px]] Senegal | style="background:#dff6df; font-weight:bold;" | I | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 0 | style="background:#dff6df; font-weight:bold;" | 2 | style="background:#dff6df; font-weight:bold;" | 8 | style="background:#dff6df; font-weight:bold;" | 6 | style="background:#dff6df; font-weight:bold;" | +2 |- | 9 | style="text-align:left;" | [[File:Flag of Iran.svg|20px]] Iran | G | 3 || 3 || 0 || 3 || 0 || 3 || 3 || 0 |- | 10 | style="text-align:left;" | [[File:Flag of South Korea.svg|20px]] Pôłniowô Korea | A | 3 || 3 || 1 || 0 || 2 || 2 || 3 || −1 |- | 11 | style="text-align:left;" | [[File:Flag of Scotland.svg|20px]] Szkòckô | C | 3 || 3 || 1 || 0 || 2 || 2 || 5 || −3 |- | 12 | style="text-align:left;" | [[File:Flag of Uruguay.svg|20px]] Ùrugwaj | H | 2 || 3 || 0 || 2 || 1 || 3 || 4 || −1 |} == Pùcharowô faza == === Drôbka === {{Turniérowô drôbka | boldwinner=y | RD1= 1/16 finału | RD2= 1/8 finału | RD1-text01=29 czerwińca – [[Gillette Stadium|Foxborough]] | RD1-team01=[[File:Flag of Germany.svg|20px]] Niemieckô | RD1-score01=1 (3) | RD1-team02=[[File:Flag of Paraguay.svg|20px]] Paragwaj | RD1-score02=1 (4) | RD1-text03=28 czerwińca – [[SoFi Stadium|Inglewood]] | RD1-team03=[[File:Flag of France.svg|20px]] Francëskô | RD1-score03=3 | RD1-team04=[[File:Flag of Sweden.svg|20px]] Szwedzkô | RD1-score04=0 | RD1-text05=3 lëpińca – [[BMO Field|Toronto]] | RD1-team05=[[File:Flag of South Africa.svg|20px]] Pôłniowô Afrika | RD1-team06=[[File:Flag of Canada.svg|20px]] Kanada | RD1-score05=0 | RD1-score06=1 | RD1-text07=2 lëpińca – [[Levi’s Stadium|Santa Clara]] | RD1-team07=[[File:Flag of Netherlands.svg|20px]] Niderlandzkô | RD1-team08=[[File:Flag of Morocco.svg|20px]] Marokò | RD1-score07=1 (2) | RD1-score08=1 (3) | RD1-text09=29 czerwińca – [[NRG Stadium|Houston]] | RD1-team09=[[File:Flag of Portugal.svg|20px]] Pòrtugalskô | RD1-team10=[[File:Flag of Croatia.svg|20px]] Chòrwackô | RD1-score09=2 | RD1-score10=1 | RD1-text11=1 lëpińca – [[Estadio Azteca|Meksyk]] | RD1-team11=[[File:Flag of Spain.svg|20px]] Szpańskô | RD1-team12=[[File:Flag of Austria.svg|20px]] Aùstriackô | RD1-score11=3 | RD1-score12=0 | RD1-text13=4 lëpińca – [[Hard Rock Stadium|Miami Gardens]] | RD1-team13=[[File:Flag of United States.svg|20px]] USA | RD1-team14=[[File:Flag of Bosnia and Herzegovina.svg|20px]] Bòsniô a Hercegòwina | RD1-score13=2 | RD1-score14=0 | RD1-text15=3 lëpińca – [[BC Place Stadium|Vancouver]] | RD1-team15=[[File:Flag of Belgium.svg|20px]] Belgiô | RD1-team16=[[File:Flag of Senegal.svg|20px]] Senegal | RD1-score15=3 | RD1-score16=2 | RD1-text17= | RD1-team17=[[File:Flag of Brazil.svg|20px]] Brazylskô | RD1-team18=[[File:Flag of Japan.svg|20px]] Japòńskô | RD1-score17=2 | RD1-score18=1 | RD1-text19= | RD1-team19=[[File:Flag of Ivory Coast.svg|20px]] Ùbrzég Słoniowëch Kłów | RD1-team20=[[File:Flag of Norway.svg|20px]] Norweskô | RD1-score19=1 | RD1-score20=2 | RD1-text21= | RD1-team21=[[File:Flag of Mexico.svg|20px]] Meksyk | RD1-team22=[[File:Flag of Ecuador.svg|20px]] Ekwador | RD1-score21=2 | RD1-score22=0 | RD1-text23= | RD1-team23=[[File:Flag of England.svg|20px]] Anielskô | RD1-team24=[[File:Flag of the Democratic Republic of the Congo.svg|20px]] DRK | RD1-score23=2 | RD1-score24=1 | RD1-text25= | RD1-team25=[[File:Flag of Argentina.svg|20px]] Argentińskô | RD1-team26=[[File:Flag of Cape Verde.svg|20px]] Cabo Verde | RD1-score25=3 | RD1-score26=2 | RD1-text27= | RD1-team27=[[File:Flag of Australia.svg|20px]] Aùstraliô | RD1-team28=[[File:Flag of Egypt.svg|20px]] Egipt | RD1-score27=1 (2) | RD1-score28=1 (4) | RD1-text29= | RD1-team29=[[File:Flag of Switzerland.svg|20px]] Szwajcarskô | RD1-team30=[[File:Flag of Algeria.svg|20px]] Algierskô | RD1-score29=2 | RD1-score30=0 | RD1-text31= | RD1-team31=[[File:Flag of Colombia.svg|20px]] Kòlumbiô | RD1-team32=[[File:Flag of Ghana.svg|20px]] Ghana | RD1-score31=1 | RD1-score32=0 | RD2-text01=4 lëpińca – [[Lincoln Financial Field|Filadelfia]] | RD2-team01=[[File:Flag of Paraguay.svg|20px]] Paragwaj | RD2-team02=[[File:Flag of France.svg|20px]] Francëskô | RD2-score01=0 | RD2-score02=1 | RD2-text02=4 lëpińca – [[NRG Stadium|Houston]] | RD2-team03=[[File:Flag of Canada.svg|20px]] Kanada | RD2-team04=[[File:Flag of Morocco.svg|20px]] Marokò | RD2-score03=0 | RD2-score04=3 | RD2-text03=6 lëpińca – [[AT&T Stadium|Arlington]] | RD2-team05=[[File:Flag of Portugal.svg|20px]] Pòrtugalskô | RD2-team06=[[File:Flag of Spain.svg|20px]] Szpańskô | RD2-score05= | RD2-score06= | RD2-text04=7 lëpińca – [[CenturyLink Field|Seattle]] | RD2-team07=[[File:Flag of USA.svg|20px]] USA | RD2-team08=[[File:Flag of Belgium.svg|20px]] Belgiô | RD2-score07= | RD2-score08= | RD2-text05=5 lëpińca – [[MetLife Stadium|East Rutherford]] | RD2-team09=[[File:Flag of Brazil.svg|20px]] Brazylskô | RD2-team10=[[File:Flag of Norway.svg|20px]] Norweskô | RD2-score09= | RD2-score10= | RD2-text06=6 lëpińca – [[Estadio Azteca|Meksyk]] | RD2-team11=[[File:Flag of Mexico.svg|20px]] Meksyk | RD2-team12=[[File:Flag of England.svg|20px]] Anielskô | RD2-score11= | RD2-score12= | RD2-text07=7 lëpińca – [[Mercedes-Benz Stadium|Atlanta]] | RD2-team13=[[File:Flag of Argentina.svg|20px]] Argentińskô | RD2-team14=[[File:Flag of Egypt.svg|20px]] Egipt | RD2-score13= | RD2-score14= | RD2-text08=7 lëpińca – [[BC Place Stadium|Vancouver]] | RD2-team15=[[File:Flag of Switzerland.svg|20px]] Szwajcarskô | RD2-team16=[[File:Flag of Colombia.svg|20px]] Kòlumbiô | RD2-score15= | RD2-score16= | RD3-text01=9 lëpińca – [[Gillette Stadium|Foxborough]] | RD3-team01=[[File:Flag of Morocco.svg|20px]] Marokò | RD3-team02=[[File:Flag of France.svg|20px]] Francëskô | RD3-score01= | RD3-score02= | RD3-text02=10 lëpińca – [[SoFi Stadium|Inglewood]] | RD3-team03=Dobiwca meczu 93 | RD3-team04=Dobiwca meczu 94 | RD3-score03= | RD3-score04= | RD3-text03=11 lëpińca – [[Hard Rock Stadium|Miami Gardens]] | RD3-team05=Dobiwca meczu 91 | RD3-team06=Dobiwca meczu 92 | RD3-score05= | RD3-score06= | RD3-text04=12 lëpińca – [[Arrowhead Stadium|Kansas City]] | RD3-team07=Dobiwca meczu 95 | RD3-team08=Dobiwca meczu 96 | RD3-score07= | RD3-score08= | RD4-text01=14 lëpińca – [[AT&T Stadium|Arlington]] | RD4-team01=Dobiwca meczu 97 | RD4-team02=Dobiwca meczu 98 | RD4-score01= | RD4-score02= | RD4-text02=15 lëpińca – [[Mercedes-Benz Stadium|Atlanta]] | RD4-team03=Dobiwca meczu 99 | RD4-team04=Dobiwca meczu 100 | RD4-score03= | RD4-score04= | RD5-text01=19 lëpińca – [[MetLife Stadium|East Rutherford]] | RD5-team01=Dobiwca meczu 101 | RD5-team02=Dobiwca meczu 102 | RD5-score01= | RD5-score02= | RD5b-text01=18 lëpińca – [[Hard Rock Stadium|Miami Gardens]] | RD5b-team01=Przerżnóny meczu 101 | RD5b-team02=Przerżnóny meczu 102 | RD5b-score01= | RD5b-score02= }} === 1/16 finału === {{fùsbalowi mecz | etap = Mecz 73 | data = 28 czerwińca 2026, 21:00 | stadión = SoFi Stadium, Los Angeles | druzyna1 = Pôłniowô Afrika | fana1 = RSA | wënik1 = 0 | druzyna2 = Kanada | fana2 = Canada | wënik2 = 1 | gòle2 = Eustáquio 90+2 | òbzérôczë = 69 237 | sãdza = João Pinheiro }} <br> ---- {{fùsbalowi mecz | etap = Mecz 74 | data = 29 czerwińca 2026, 19:00 | stadión = NRG Stadium, Houston | druzyna1 = Brazylskô | fana1 = Brazil | wënik1 = 2 | druzyna2 = Japòńskô | fana2 = Japan | wënik2 = 1 | gòle1 = Casemiro 56’, Martinelli 90+5 | gòle2 = Sano 29’ | òbzérôczë = 68 777 | sãdza = Maurizio Mariani }} <br> ---- {{fùsbalowi mecz | etap = Mecz 75 | data = 29 czerwińca 2026, 22:30 | stadión = Gillette Stadium, Boston | druzyna1 = Niemieckô | fana1 = Germany | wënik1 = 1 | druzyna2 = Paragwaj | fana2 = Paraguay | wënik2 = 1 | karné = 3-4 | gòle1 = Havertz 54’ | gòle2 = Enciso 42’ | òbzérôczë = 63 945 | sãdza = Jalal Jayed }} <br> ---- {{fùsbalowi mecz | etap = Mecz 76 | data = 30 czerwińca 2026, 03:00 | stadión = Estadio BBVA, Monterrey | druzyna1 = Niderlandzkô | fana1 = Netherlands | wënik1 = 1 | gòle1 = Gakpo 72’ | druzyna2 = Marokò | fana2 = Morocco | wënik2 = 1 | karné = 2-3 | gòle2 = Diop 90+1 | òbzérôczë = 68 777 | sãdza = Wilton Sampaio }} <br> ---- {{fùsbalowi mecz | etap = Mecz 77 | data = 30 czerwińca 2026, 19:00 | stadión = AT&T Stadium, Arlington | druzyna1 = Ùbrzég Słoniowëch Kłów | fana1 = Ivory Coast | wënik1 = 1 | gòle1 = Diallo 74’ | druzyna2 = Norweskô | fana2 = Norway | wënik2 = 2 | gòle2 = Nusa 39’, Haaland 86’ | òbzérôczë = 69 665 | sãdza = Jesús Valenzuela }} <br> ---- {{fùsbalowi mecz | etap = Mecz 78 | data = 30 czerwińca 2026, 23:00 | stadión = MetLife Stadium, East Rutherford | druzyna1 = Francëskô | fana1 = France | wënik1 = 3 | druzyna2 = Szwedzkô | fana2 = Sweden | wënik2 = 0 | gòle1 = Mbappé 45’, 74’; Barcola 53’ | òbzérôczë = 80 663 | sãdza = Danny Makkelie }} <br> ---- {{fùsbalowi mecz | etap = Mecz 79 | data = 1 lëpińca 2026, 03:00 | stadión = Estadio Azteca, Meksyk | druzyna1 = Meksyk | fana1 = Mexico | wënik1 = 2 | druzyna2 = Ekwador | fana2 = Ecuador | wënik2 = 0 | gòle1 = Quiñones 22’, Jiménez 31’ | òbzérôczë = 80 824 | sãdza = Slavko Vinčić }} <br> ---- {{fùsbalowi mecz | etap = Mecz 80 | data = 1 lëpińca 2026, 18:00 | stadión = Mercedes-Benz Stadium, Atlanta | druzyna1 = Anielskô | fana1 = England | wënik1 = 2 | druzyna2 = Demòkraticznô Repùblika Kònga | fana2 = Democratic Republic of the Congo | wënik2 = 1 | gòle1 = Kane 75’, 86’ | gòle2 = Cipenga 7’ | òbzérôczë = 68 239 | sãdza = Adham Makhadmeh }} <br> ---- {{fùsbalowi mecz | etap = Mecz 81 | data = 1 lëpińca 2026, 22:00 | stadión = Lumen Field, Seattle | druzyna1 = Belgiô | fana1 = Belgium | wënik1 = 3 | gòle1 = Lukaku 86’; Tielemans 89’, 120+5' | druzyna2 = Senegal | fana2 = Senegal | wënik2 = 2 | gòle2 = Diarra 24’, Sarr 51’ | òbzérôczë = 66 925 | sãdza = Said Martínez }} <br> ---- {{fùsbalowi mecz | etap = Mecz 82 | data = 2 lëpińca 2026, 02:00 | stadión = Levi’s Stadium, Santa Clara | druzyna1 = USA | fana1 = USA | wënik1 = 2 | gòle1 = Balogun 78’, Tillman 82' | druzyna2 = Bòsniô a Hercegòwina | fana2 = Bosnia and Herzegovina | wënik2 = 0 | òbzérôczë = 68 827 | sãdza = Raphael Claus }} <br> ---- {{fùsbalowi mecz | etap = Mecz 83 | data = 2 lëpińca 2026, 22:00 | stadión = SoFi Stadium, Los Angeles | druzyna1 = Szpańskô | fana1 = Spain | wënik1 = 3 | gòle1 = Oyarzabal 36', 89'; Porro 66' | druzyna2 = Aùstriackô | fana2 = Austria | wënik2 = 0 | òbzérôczë = 70 492 | sãdza = Glenn Nyberg }} <br> ---- {{fùsbalowi mecz | etap = Mecz 84 | data = 2 lëpińca 2026, 18:00 | stadión = GEHA Field at Arrowhead Stadium, Kansas City | druzyna1 = Pòrtugalskô | fana1 = Portugal | wënik1 = 2 | gòle1 = Cristiano Ronaldo 68' (k.), Gonçalo Ramos 90+4' | druzyna2 = Chòrwackô | fana2 = Croatia | wënik2 = 1 | gòle2 = Perišić 41' | òbzérôczë = 43 036 | sãdza = Espen Eskas }} <br> ---- {{fùsbalowi mecz | etap = Mecz 85 | data = 3 lëpińca 2026, 02:00 | stadión = BC Place, Vancouver | druzyna1 = Szwajcarskô | fana1 = Switzerland | wënik1 = 2 | gòle1 = Embolo 10'; Ndoye 46' | druzyna2 = Algerskô | fana2 = Algeria | wënik2 = 0 | òbzérôczë = 52 497 | sãdza = Yael Falcon Perez }} <br> ---- {{fùsbalowi mecz | etap = Mecz 86 | data = 3 lëpińca 2026, 20:00 | stadión = AT&T Stadium, Arlington | druzyna1 = Aùstraliô | fana1 = Australia | wënik1 = 1 | druzyna2 = Egipt | fana2 = Egypt | wënik2 = 1 | karné = 2-4 | gòle1 = Hany 55' | gòle2 = Ashour 13' | òbzérôczë = 70 244 | sãdza = Gustavo Tejera }} <br> ---- {{fùsbalowi mecz | etap = Mecz 87 | data = 3 lëpińca 2026, 22:00 | stadión = Hard Rock Stadium, Miami Gardens | druzyna1 = Argentińskô | fana1 = Argentina | wënik1 = 3 | gòle1 = Messi 29'; Lautaro Martínez 92'; Diney 111' | druzyna2 = Cabo Verde | fana2 = Cape Verde | wënik2 = 2 | gòle2 = D. Duarte 59'; Lopes Cabral 103' | òbzérôczë = 64 478 | sãdza = Drew Fischer }} <br> ------- {{fùsbalowi mecz | etap = Mecz 88 | data = 4 lëpińca 2026, 02:30 | stadión = GEHA Field at Arrowhead Stadium, Kansas City | druzyna1 = Kòlumbiô | fana1 = Colombia | wënik1 = 1 | gòle1 = J. Arias 14' | druzyna2 = Ghana | fana2 = Ghana | wënik2 = 0 | òbzérôczë = 69 045 | sãdza = Clément Turpin }} === 1/8 finału === {{fùsbalowi mecz | etap = Mecz 89 | data = 4 lëpińca 2026, 13:00 | stadión = NRG Stadium, Houston | druzyna1 = Kanada | fana1 = Canada | wënik1 = 0 | druzyna2 = Marokò | fana2 = Morocco | wënik2 = 3 | gòle2 = Ounahi 50’, 82’; Rahimi 90+8’ | òbzérôczë = 68 777 | sãdza = Michael Oliver }} <br> ---- {{fùsbalowi mecz | etap = Mecz 90 | data = 4 lëpińca 2026, 21:00 | stadión = MetLife Stadium, East Rutherford | druzyna1 = Paragwaj | fana1 = Paraguay | wënik1 = 0 | druzyna2 = Francëskô | fana2 = France | wënik2 = 1 | gòle2 = Mbappé 70' (k.) | òbzérôczë = 68 324 | sãdza = Ilgiz Tantashev }} <br> '''Bôczënk:''' jinfòrmacje zrzeszoné z pòstãpnyma zeńdzeniama mdą aktualizowóné przez całi czas. == Finał == Finał òdbëwa sã [[19 lëpińca]] 2026 rokù na stadionie ''MetLife Stadium'' w East Rutherford (aglomeracjô [[Nowi Jork|Nowégò Jorkù]]). == Òbaczë téż == {{#categorytree:Nożnô bala|hideroot=on|mode=pages}} == Przëpisë == {{Przëpisë}} == Bùtnowé lënczi == {{Commons}} {{Kòntrola}} [[Kategòrëjô:Nożnô bala]] rdlva3g4q2tobr9v215istt08xo45m1 204490 204447 2026-07-05T22:24:52Z HaxelKaszëba 17492 norweskô dobéła 204490 wikitext text/x-wiki {{Spòrtowi Turniér infobox|gòspòdôrzë=[[Òbrôzk:Flag of Canada.svg|tło|22x22px]] [[Kanada]] [[Òbrôzk:Flag of Mexico.svg|tło|22x22px]] [[Meksyk]] [[Òbrôzk:Flag of the United States.svg|tło|22x22px]] [[Zjednóné Kraje Americzi|Zjednóné Krôje Americzi]]|data=11 czerwińca 2026|pòzwa=Mésterstwò Swiata w fùsbalë 2026<br>FIFA World Cup 2026<br>Coupe du monde de football de 2026<br>Copa Mundial de Fútbol 2026|òbrôzk=2026 FIFA World Cup emblem (with wordmark).svg|reprezentacje=48|stadiónë=16|mecze=91|gòle= 264|ùszłô=[[Mésterstwò Swiata w fùsbalë 2022|2022]]|aktualné=2026]}} '''XXIII Mésterstwò Swiata w fùsbalë 2026''' – 23. edicjô mésterstwô swiata twòrzoné przez [[FIFA]]. Turniér òdbëwô sã òd [[11 czerwińca]] do [[19 lëpińca]] 2026 rokù na stadionach w [[Kanada|Kanadze]], [[Meksyk|Meksykù]] a [[Zjednóné Kraje Americzi|Zjednónëch Krôjach]]<ref>FIFA: ''[https://www.fifa.com/en/tournaments/mens/worldcup/canadamexicousa2026 FIFA World Cup 2026 Official Website]''. [przistãp 2026-06-29].</ref>. Je to pierszi mùndial twòrzony wespólnie przez trzë krôje òrôz pierszi w historie z dzélã 48 nôrodnëch reprezentacjów. Nowi fòrmat turniéru òbjimùje 12 grëp pò szterëch karnów, z jaczich do pùcharowi fazë awansëje dwie nôlepszé reprezentacje z kòżdi grëpë òrôz òsmë nôlepszich karnów z trzecëch placów. Òznôczô to rozegranié rekordowi lëczbë 104 zeńdzeniów<ref>FIFA: ''[https://www.fifa.com/en/tournaments/mens/worldcup/canadamexicousa2026/articles/fifa-council-approves-international-match-calendar-new-format FIFA Council approves international match calendar and 104-match World Cup format]''. [przistãp 2026-06-29].</ref>. == Welënk gòspòdôrzów == [[Òbrôzk:2026 world cup.svg|mały|Wëniczi w òsoblëwëch krôjach]] 13 czerwińca 2018 rokù òb czas Kòngresu FIFA w Mòskwie dobéła wespólna kandidatura Kanadë, Meksykù a Zjednónëch Krôjów, przedobiwôjącë bédënk [[Marokò|Maroka]]<ref>FIFA: ''[https://www.fifa.com/en/tournaments/mens/worldcup/canadamexicousa2026/articles/united-2026-bid-wins-hosts-fifa-world-cup United 2026 bid voted hosts of the 2026 FIFA World Cup]''. [przistãp 2026-06-29].</ref>. Turniér je historiczny z czilu pòwodów: * pierszi mundial twòrzony przez trzë krôje; * Meksyk òstôł pierszim krôjã, jaczi gòscy mésterstwò swiata pò rôz trzecy; * Kanada pò rôz pierszi twòrzy chłopsczi mésterstwò swiata; * Zjednóné Krôje twòrzą mundial pò rôz drëdżi (pò 1994 rokù). Hewò wëniczi welowaniô: {| class="wikitable" style="text-align: center;" !Państwò !Głosë |- | align="left" |[[Òbrôzk:Flag of Canada.svg|tło|22x22px]] [[Kanada]] [[Òbrôzk:Flag of Mexico.svg|tło|22x22px]] [[Meksyk]] [[Òbrôzk:Flag of the United States.svg|tło|22x22px]] [[Zjednóné Kraje Americzi|Zjednóné Krôje Americzi]] |'''134''' |- | align="left" | [[Òbrôzk:Flag of Morocco.svg|tło|22x22px]] [[Marokò]] |65 |} == Fòrmat rozgriwków == Pò rôz pierszi w historie w finałach wëstãpùje 48 reprezentacjów. [[Òbrôzk:FIFA World Cup 2026 Draw Reception (54972845582).jpg|mały|Òficjalnô bala na mésterstwô, Trionda]] Fòrmat òbjimùje: * 12 grëp pò 4 karnów, * Kòżdé karno rozgriwô 3 grëpowé mecze, * awans zwëskiwają: ** dobiwcë grëp, ** karnë z drëdżich placów, ** òsmë nôlepszich reprezentacjów z trzecych placów, * òd pùcharowi fazë òbrzeszëwùje pùcharowi system rozpòczinôjącë sã òd 1/16 finału (Round of 32). == Gardë gòspòdôrzów == Turniér òdbëwô sã na 16 stadionach<ref>FIFA: ''[https://www.fifa.com/en/tournaments/mens/worldcup/canadamexicousa2026/articles/fifa-unveils-stellar-line-up-of-host-cities-for-2026-world-cup FIFA unveils stellar line-up of host cities for 2026 FIFA World Cup]''. [przistãp 2026-06-29].</ref>. === [[Kanada]] === * Toronto * Vancouver === [[Meksyk]] === * Meksyk * Guadalajara * Monterrey === [[Zjednóné Kraje Americzi|Zjednóné Krôje]] === * Atlanta * Boston * Dallas * Houston * Kansas City * Los Angeles * Miami * [[Nowi Jork]] / East Rutherford * Filadelfia * San Francisco Bay Area (Santa Clara) * Seattle == Grëpòwa faza == Reprezentacje òstôłë pòdzelóné na 12 grëp òznôczonëch lëterama òd A do L. Kòżdé karno rozgriwô trzë zeńdzeniô. === Grëpa A === {{#invoke:Grëpa MS|render |group=Grëpa A |1=[[File:Flag of Mexico.svg|20px]] Meksyk |1pkt=9 |1m=3 |1w=3 |1r=0 |1p=0 |1g+=6 |1g-=0 |1rg=+6 |2=[[File:Flag of South Africa.svg|20px]] Pôłniowô Afrika |2pkt=4 |2m=3 |2w=1 |2r=1 |2p=1 |2g+=2 |2g-=3 |2rg=-1 |3=[[File:Flag of South Korea.svg|20px]] Pôłniowô Korea |3pkt=3 |3m=3 |3w=1 |3r=0 |3p=2 |3g+=2 |3g-=3 |3rg=-1 |4=[[File:Flag of Czech Republic.svg|20px]] Czeskô |4pkt=1 |4m=3 |4w=0 |4r=1 |4p=2 |4g+=2 |4g-=4 |4rg=-2 }} === Grëpa B === {{#invoke:Grëpa MS|render |group=Grëpa B |advance=3 |1=[[File:Flag of Switzerland.svg|20px]] Szwajcarskô |1pkt=7 |1m=3 |1w=2 |1r=1 |1p=0 |1g+=6 |1g-=3 |1rg=+3 |2=[[File:Flag of Canada.svg|20px]] Kanada |2pkt=4 |2m=3 |2w=1 |2r=1 |2p=1 |2g+=5 |2g-=4 |2rg=+1 |3=[[File:Flag of Bosnia and Herzegovina.svg|20px]] Bòsniô a Hercegòwina |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=4 |3g-=5 |3rg=-1 |4=[[File:Flag of Qatar.svg|20px]] Katar |4pkt=1 |4m=3 |4w=0 |4r=1 |4p=2 |4g+=2 |4g-=9 |4rg=-7 }} === Grëpa C === {{#invoke:Grëpa MS|render |group=Grëpa C |1=[[File:Flag of Brazil.svg|20px]] Brazylskô |1pkt=7 |1m=3 |1w=2 |1r=1 |1p=0 |1g+=7 |1g-=1 |1rg=+6 |2=[[File:Flag of Morocco.svg|20px]] Marokò |2pkt=7 |2m=3 |2w=2 |2r=1 |2p=0 |2g+=6 |2g-=3 |2rg=+3 |3=[[File:Flag of Scotland.svg|20px]] Szkòckô |3pkt=3 |3m=3 |3w=1 |3r=0 |3p=2 |3g+=2 |3g-=5 |3rg=-3 |4=[[File:Flag of Haiti.svg|20px]] Haiti |4pkt=0 |4m=3 |4w=0 |4r=0 |4p=3 |4g+=2 |4g-=8 |4rg=-6 }} === Grëpa D === {{#invoke:Grëpa MS|render |group=Grëpa D |advance=3 |1=[[File:Flag of United States.svg|20px]] USA |1pkt=6 |1m=3 |1w=2 |1r=0 |1p=1 |1g+=8 |1g-=4 |1rg=+4 |2=[[File:Flag of Australia.svg|20px]] Aùstraliô |2pkt=4 |2m=3 |2w=1 |2r=1 |2p=1 |2g+=2 |2g-=2 |2rg=0 |3=[[File:Flag of Paraguay.svg|20px]] Paragwaj |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=3 |3g-=4 |3rg=-1 |4=[[File:Flag of Turkey.svg|20px]] Tëreckô |4pkt=3 |4m=3 |4w=1 |4r=0 |4p=2 |4g+=3 |4g-=5 |4rg=-2 }} === Grëpa E === {{#invoke:Grëpa MS|render |group=Grëpa E |advance=3 |1=[[File:Flag of Germany.svg|20px]] Niemieckô |1pkt=6 |1m=3 |1w=2 |1r=0 |1p=1 |1g+=10 |1g-=4 |1rg=+6 |2=[[File:Flag of Ivory Coast.svg|20px]] Ùbrzég Słoniowëch Kłów |2pkt=6 |2m=3 |2w=2 |2r=0 |2p=1 |2g+=4 |2g-=2 |2rg=+2 |3=[[File:Flag of Ecuador.svg|20px]] Ekwador |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=2 |3g-=2 |3rg=0 |4=[[File:Flag of Curaçao.svg|20px]] Curaçao |4pkt=1 |4m=3 |4w=0 |4r=1 |4p=2 |4g+=1 |4g-=9 |4rg=-8 }} === Grëpa F === {{#invoke:Grëpa MS|render |group=Grëpa F |advance=3 |1=[[File:Flag of Netherlands.svg|20px]] Niderlandzkô |1pkt=7 |1m=3 |1w=2 |1r=1 |1p=0 |1g+=10 |1g-=4 |1rg=+6 |2=[[File:Flag of Japan.svg|20px]] Japòńskô |2pkt=5 |2m=3 |2w=1 |2r=2 |2p=0 |2g+=7 |2g-=3 |2rg=+4 |3=[[File:Flag of Sweden.svg|20px]] Szwedzkô |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=7 |3g-=7 |3rg=0 |4=[[File:Flag of Tunisia.svg|20px]] Tunezjô |4pkt=0 |4m=3 |4w=0 |4r=0 |4p=3 |4g+=2 |4g-=12 |4rg=-10 }} === Grëpa G === {{#invoke:Grëpa MS|render |group=Grëpa G |1=[[File:Flag of Belgium.svg|20px]] Belgiô |1pkt=5 |1m=3 |1w=1 |1r=2 |1p=0 |1g+=6 |1g-=2 |1rg=+4 |2=[[File:Flag of Egypt.svg|20px]] Egipt |2pkt=5 |2m=3 |2w=1 |2r=2 |2p=0 |2g+=5 |2g-=3 |2rg=+2 |3=[[File:Flag of Iran.svg|20px]] Iran |3pkt=3 |3m=3 |3w=0 |3r=3 |3p=0 |3g+=3 |3g-=3 |3rg=0 |4=[[File:Flag of New Zealand.svg|20px]] Nowô Zelandzkô |4pkt=1 |4m=3 |4w=0 |4r=1 |4p=2 |4g+=4 |4g-=10 |4rg=-6 }} === Grëpa H === {{#invoke:Grëpa MS|render |group=Grëpa H |1=[[File:Flag of Spain.svg|20px]] Szpańskô |1pkt=7 |1m=3 |1w=2 |1r=1 |1p=0 |1g+=5 |1g-=0 |1rg=+5 |2=[[File:Flag of Cape Verde.svg|20px]] Cabo Verde |2pkt=3 |2m=3 |2w=0 |2r=3 |2p=0 |2g+=2 |2g-=2 |2rg=0 |3=[[File:Flag of Uruguay.svg|20px]] Ùrugwaj |3pkt=2 |3m=3 |3w=0 |3r=2 |3p=1 |3g+=3 |3g-=4 |3rg=-1 |4=[[File:Flag of Saudi Arabia.svg|20px]] Saùdijskô Arabiô |4pkt=2 |4m=3 |4w=0 |4r=2 |4p=1 |4g+=1 |4g-=5 |4rg=-4 }} === Grëpa I === {{#invoke:Grëpa MS|render |group=Grëpa I |advance=3 |1=[[File:Flag of France.svg|20px]] Francëskô |1pkt=9 |1m=3 |1w=3 |1r=0 |1p=0 |1g+=10 |1g-=2 |1rg=+8 |2=[[File:Flag of Norway.svg|20px]] Norweskô |2pkt=6 |2m=3 |2w=2 |2r=0 |2p=1 |2g+=8 |2g-=7 |2rg=+1 |3=[[File:Flag of Senegal.svg|20px]] Senegal |3pkt=3 |3m=3 |3w=1 |3r=0 |3p=2 |3g+=8 |3g-=6 |3rg=+2 |4=[[File:Flag of Iraq.svg|20px]] Irak |4pkt=0 |4m=3 |4w=0 |4r=0 |4p=3 |4g+=1 |4g-=12 |4rg=-11 }} === Grëpa J === {{#invoke:Grëpa MS|render |group=Grëpa J |advance=3 |1=[[File:Flag of Argentina.svg|20px]] Argentińskô |1pkt=9 |1m=3 |1w=3 |1r=0 |1p=0 |1g+=8 |1g-=1 |1rg=+7 |2=[[File:Flag of Austria.svg|20px]] Aùstriackô |2pkt=4 |2m=3 |2w=1 |2r=1 |2p=1 |2g+=7 |2g-=7 |2rg=0 |3=[[File:Flag of Algeria.svg|20px]] Algierskô |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=5 |3g-=7 |3rg=-2 |4=[[File:Flag of Jordan.svg|20px]] Jordańskô |4pkt=0 |4m=3 |4w=0 |4r=0 |4p=3 |4g+=3 |4g-=8 |4rg=-5 }} === Grëpa K === {{#invoke:Grëpa MS|render |group=Grëpa K |advance=3 |1=[[File:Flag of Colombia.svg|20px]] Kòlumbiô |1pkt=7 |1m=3 |1w=2 |1r=1 |1p=0 |1g+=4 |1g-=1 |1rg=+3 |2=[[File:Flag of Portugal.svg|20px]] Pòrtugalskô |2pkt=5 |2m=3 |2w=1 |2r=2 |2p=0 |2g+=6 |2g-=1 |2rg=+5 |3=[[File:Flag of Democratic Republic of the Congo.svg|20px]] DRK |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=4 |3g-=3 |3rg=+1 |4=[[File:Flag of Uzbekistan.svg|20px]] Ùzbekistan |4pkt=0 |4m=3 |4w=0 |4r=0 |4p=3 |4g+=2 |4g-=11 |4rg=-9 }} === Grëpa L === {{#invoke:Grëpa MS|render |group=Grëpa L |advance=3 |1=[[File:Flag of England.svg|20px]] Anielskô |1pkt=7 |1m=3 |1w=2 |1r=1 |1p=0 |1g+=6 |1g-=2 |1rg=+4 |2=[[File:Flag of Croatia.svg|20px]] Chòrwackô |2pkt=6 |2m=3 |2w=2 |2r=0 |2p=1 |2g+=5 |2g-=5 |2rg=0 |3=[[File:Flag of Ghana.svg|20px]] Ghana |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=2 |3g-=2 |3rg=0 |4=[[File:Flag of Panama.svg|20px]] Panama |4pkt=0 |4m=3 |4w=0 |4r=0 |4p=3 |4g+=0 |4g-=4 |4rg=-4 }} === Ranking karnów z trzecëch placów === {| class="wikitable" style="width:100%; table-layout:fixed; text-align:center; font-family:system-ui, Arial;" <tr style="background:#f8f9fa;"> <th style="width:6%;">M.</th> <th style="width:40%;">Reprezentacjô</th> <th style="width:15%;">Grëpa</th> <th style="width:9%;">Pkt</th> <th style="width:6%;">M</th> <th style="width:6%;">W</th> <th style="width:6%;">R</th> <th style="width:6%;">P</th> <th style="width:6%;">+</th> <th style="width:6%;">–</th> <th style="width:6%;">±</th> </tr> |- | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Democratic Republic of the Congo.svg|20px]] DRK | style="background:#dff6df; font-weight:bold;" | K | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | +1 |- | style="background:#dff6df; font-weight:bold;" | 2 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Ecuador.svg|20px]] Ekwador | style="background:#dff6df; font-weight:bold;" | E | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 2 | style="background:#dff6df; font-weight:bold;" | 2 | style="background:#dff6df; font-weight:bold;" | 0 |- | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Sweden.svg|20px]] Szwedzkô | style="background:#dff6df; font-weight:bold;" | F | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 7 | style="background:#dff6df; font-weight:bold;" | 7 | style="background:#dff6df; font-weight:bold;" | 0 |- | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Bosnia and Herzegovina.svg|20px]] Bòsniô a Hercegòwina | style="background:#dff6df; font-weight:bold;" | B | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 5 | style="background:#dff6df; font-weight:bold;" | −1 |- | style="background:#dff6df; font-weight:bold;" | 5 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Paraguay.svg|20px]] Paragwaj | style="background:#dff6df; font-weight:bold;" | D | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | −1 |- | style="background:#dff6df; font-weight:bold;" | 6 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Ghana.svg|20px]] Ghana | style="background:#dff6df; font-weight:bold;" | L | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 2 | style="background:#dff6df; font-weight:bold;" | 2 | style="background:#dff6df; font-weight:bold;" | 0 |- | style="background:#dff6df; font-weight:bold;" | 7 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Algeria.svg|20px]] Algierskô | style="background:#dff6df; font-weight:bold;" | J | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 5 | style="background:#dff6df; font-weight:bold;" | 7 | style="background:#dff6df; font-weight:bold;" | −2 |- | style="background:#dff6df; font-weight:bold;" | 8 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Senegal.svg|20px]] Senegal | style="background:#dff6df; font-weight:bold;" | I | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 0 | style="background:#dff6df; font-weight:bold;" | 2 | style="background:#dff6df; font-weight:bold;" | 8 | style="background:#dff6df; font-weight:bold;" | 6 | style="background:#dff6df; font-weight:bold;" | +2 |- | 9 | style="text-align:left;" | [[File:Flag of Iran.svg|20px]] Iran | G | 3 || 3 || 0 || 3 || 0 || 3 || 3 || 0 |- | 10 | style="text-align:left;" | [[File:Flag of South Korea.svg|20px]] Pôłniowô Korea | A | 3 || 3 || 1 || 0 || 2 || 2 || 3 || −1 |- | 11 | style="text-align:left;" | [[File:Flag of Scotland.svg|20px]] Szkòckô | C | 3 || 3 || 1 || 0 || 2 || 2 || 5 || −3 |- | 12 | style="text-align:left;" | [[File:Flag of Uruguay.svg|20px]] Ùrugwaj | H | 2 || 3 || 0 || 2 || 1 || 3 || 4 || −1 |} == Pùcharowô faza == === Drôbka === {{Turniérowô drôbka | boldwinner=y | RD1= 1/16 finału | RD2= 1/8 finału | RD1-text01=29 czerwińca – [[Gillette Stadium|Foxborough]] | RD1-team01=[[File:Flag of Germany.svg|20px]] Niemieckô | RD1-score01=1 (3) | RD1-team02=[[File:Flag of Paraguay.svg|20px]] Paragwaj | RD1-score02=1 (4) | RD1-text03=28 czerwińca – [[SoFi Stadium|Inglewood]] | RD1-team03=[[File:Flag of France.svg|20px]] Francëskô | RD1-score03=3 | RD1-team04=[[File:Flag of Sweden.svg|20px]] Szwedzkô | RD1-score04=0 | RD1-text05=3 lëpińca – [[BMO Field|Toronto]] | RD1-team05=[[File:Flag of South Africa.svg|20px]] Pôłniowô Afrika | RD1-team06=[[File:Flag of Canada.svg|20px]] Kanada | RD1-score05=0 | RD1-score06=1 | RD1-text07=2 lëpińca – [[Levi’s Stadium|Santa Clara]] | RD1-team07=[[File:Flag of Netherlands.svg|20px]] Niderlandzkô | RD1-team08=[[File:Flag of Morocco.svg|20px]] Marokò | RD1-score07=1 (2) | RD1-score08=1 (3) | RD1-text09=29 czerwińca – [[NRG Stadium|Houston]] | RD1-team09=[[File:Flag of Portugal.svg|20px]] Pòrtugalskô | RD1-team10=[[File:Flag of Croatia.svg|20px]] Chòrwackô | RD1-score09=2 | RD1-score10=1 | RD1-text11=1 lëpińca – [[Estadio Azteca|Meksyk]] | RD1-team11=[[File:Flag of Spain.svg|20px]] Szpańskô | RD1-team12=[[File:Flag of Austria.svg|20px]] Aùstriackô | RD1-score11=3 | RD1-score12=0 | RD1-text13=4 lëpińca – [[Hard Rock Stadium|Miami Gardens]] | RD1-team13=[[File:Flag of United States.svg|20px]] USA | RD1-team14=[[File:Flag of Bosnia and Herzegovina.svg|20px]] Bòsniô a Hercegòwina | RD1-score13=2 | RD1-score14=0 | RD1-text15=3 lëpińca – [[BC Place Stadium|Vancouver]] | RD1-team15=[[File:Flag of Belgium.svg|20px]] Belgiô | RD1-team16=[[File:Flag of Senegal.svg|20px]] Senegal | RD1-score15=3 | RD1-score16=2 | RD1-text17= | RD1-team17=[[File:Flag of Brazil.svg|20px]] Brazylskô | RD1-team18=[[File:Flag of Japan.svg|20px]] Japòńskô | RD1-score17=2 | RD1-score18=1 | RD1-text19= | RD1-team19=[[File:Flag of Ivory Coast.svg|20px]] Ùbrzég Słoniowëch Kłów | RD1-team20=[[File:Flag of Norway.svg|20px]] Norweskô | RD1-score19=1 | RD1-score20=2 | RD1-text21= | RD1-team21=[[File:Flag of Mexico.svg|20px]] Meksyk | RD1-team22=[[File:Flag of Ecuador.svg|20px]] Ekwador | RD1-score21=2 | RD1-score22=0 | RD1-text23= | RD1-team23=[[File:Flag of England.svg|20px]] Anielskô | RD1-team24=[[File:Flag of the Democratic Republic of the Congo.svg|20px]] DRK | RD1-score23=2 | RD1-score24=1 | RD1-text25= | RD1-team25=[[File:Flag of Argentina.svg|20px]] Argentińskô | RD1-team26=[[File:Flag of Cape Verde.svg|20px]] Cabo Verde | RD1-score25=3 | RD1-score26=2 | RD1-text27= | RD1-team27=[[File:Flag of Australia.svg|20px]] Aùstraliô | RD1-team28=[[File:Flag of Egypt.svg|20px]] Egipt | RD1-score27=1 (2) | RD1-score28=1 (4) | RD1-text29= | RD1-team29=[[File:Flag of Switzerland.svg|20px]] Szwajcarskô | RD1-team30=[[File:Flag of Algeria.svg|20px]] Algierskô | RD1-score29=2 | RD1-score30=0 | RD1-text31= | RD1-team31=[[File:Flag of Colombia.svg|20px]] Kòlumbiô | RD1-team32=[[File:Flag of Ghana.svg|20px]] Ghana | RD1-score31=1 | RD1-score32=0 | RD2-text01=4 lëpińca – [[Lincoln Financial Field|Filadelfia]] | RD2-team01=[[File:Flag of Paraguay.svg|20px]] Paragwaj | RD2-team02=[[File:Flag of France.svg|20px]] Francëskô | RD2-score01=0 | RD2-score02=1 | RD2-text02=4 lëpińca – [[NRG Stadium|Houston]] | RD2-team03=[[File:Flag of Canada.svg|20px]] Kanada | RD2-team04=[[File:Flag of Morocco.svg|20px]] Marokò | RD2-score03=0 | RD2-score04=3 | RD2-text03=6 lëpińca – [[AT&T Stadium|Arlington]] | RD2-team05=[[File:Flag of Portugal.svg|20px]] Pòrtugalskô | RD2-team06=[[File:Flag of Spain.svg|20px]] Szpańskô | RD2-score05= | RD2-score06= | RD2-text04=7 lëpińca – [[CenturyLink Field|Seattle]] | RD2-team07=[[File:Flag of USA.svg|20px]] USA | RD2-team08=[[File:Flag of Belgium.svg|20px]] Belgiô | RD2-score07= | RD2-score08= | RD2-text05=5 lëpińca – [[MetLife Stadium|East Rutherford]] | RD2-team09=[[File:Flag of Brazil.svg|20px]] Brazylskô | RD2-team10=[[File:Flag of Norway.svg|20px]] Norweskô | RD2-score09= | RD2-score10= | RD2-text06=6 lëpińca – [[Estadio Azteca|Meksyk]] | RD2-team11=[[File:Flag of Mexico.svg|20px]] Meksyk | RD2-team12=[[File:Flag of England.svg|20px]] Anielskô | RD2-score11= | RD2-score12= | RD2-text07=7 lëpińca – [[Mercedes-Benz Stadium|Atlanta]] | RD2-team13=[[File:Flag of Argentina.svg|20px]] Argentińskô | RD2-team14=[[File:Flag of Egypt.svg|20px]] Egipt | RD2-score13= | RD2-score14= | RD2-text08=7 lëpińca – [[BC Place Stadium|Vancouver]] | RD2-team15=[[File:Flag of Switzerland.svg|20px]] Szwajcarskô | RD2-team16=[[File:Flag of Colombia.svg|20px]] Kòlumbiô | RD2-score15= | RD2-score16= | RD3-text01=9 lëpińca – [[Gillette Stadium|Foxborough]] | RD3-team01=[[File:Flag of Morocco.svg|20px]] Marokò | RD3-team02=[[File:Flag of France.svg|20px]] Francëskô | RD3-score01= | RD3-score02= | RD3-text02=10 lëpińca – [[SoFi Stadium|Inglewood]] | RD3-team03=Dobiwca meczu 93 | RD3-team04=Dobiwca meczu 94 | RD3-score03= | RD3-score04= | RD3-text03=11 lëpińca – [[Hard Rock Stadium|Miami Gardens]] | RD3-team05=Dobiwca meczu 91 | RD3-team06=Dobiwca meczu 92 | RD3-score05= | RD3-score06= | RD3-text04=12 lëpińca – [[Arrowhead Stadium|Kansas City]] | RD3-team07=Dobiwca meczu 95 | RD3-team08=Dobiwca meczu 96 | RD3-score07= | RD3-score08= | RD4-text01=14 lëpińca – [[AT&T Stadium|Arlington]] | RD4-team01=Dobiwca meczu 97 | RD4-team02=Dobiwca meczu 98 | RD4-score01= | RD4-score02= | RD4-text02=15 lëpińca – [[Mercedes-Benz Stadium|Atlanta]] | RD4-team03=Dobiwca meczu 99 | RD4-team04=Dobiwca meczu 100 | RD4-score03= | RD4-score04= | RD5-text01=19 lëpińca – [[MetLife Stadium|East Rutherford]] | RD5-team01=Dobiwca meczu 101 | RD5-team02=Dobiwca meczu 102 | RD5-score01= | RD5-score02= | RD5b-text01=18 lëpińca – [[Hard Rock Stadium|Miami Gardens]] | RD5b-team01=Przerżnóny meczu 101 | RD5b-team02=Przerżnóny meczu 102 | RD5b-score01= | RD5b-score02= }} === 1/16 finału === {{fùsbalowi mecz | etap = Mecz 73 | data = 28 czerwińca 2026, 21:00 | stadión = SoFi Stadium, Los Angeles | druzyna1 = Pôłniowô Afrika | fana1 = RSA | wënik1 = 0 | druzyna2 = Kanada | fana2 = Canada | wënik2 = 1 | gòle2 = Eustáquio 90+2 | òbzérôczë = 69 237 | sãdza = João Pinheiro }} <br> ---- {{fùsbalowi mecz | etap = Mecz 74 | data = 29 czerwińca 2026, 19:00 | stadión = NRG Stadium, Houston | druzyna1 = Brazylskô | fana1 = Brazil | wënik1 = 2 | druzyna2 = Japòńskô | fana2 = Japan | wënik2 = 1 | gòle1 = Casemiro 56’, Martinelli 90+5 | gòle2 = Sano 29’ | òbzérôczë = 68 777 | sãdza = Maurizio Mariani }} <br> ---- {{fùsbalowi mecz | etap = Mecz 75 | data = 29 czerwińca 2026, 22:30 | stadión = Gillette Stadium, Boston | druzyna1 = Niemieckô | fana1 = Germany | wënik1 = 1 | druzyna2 = Paragwaj | fana2 = Paraguay | wënik2 = 1 | karné = 3-4 | gòle1 = Havertz 54’ | gòle2 = Enciso 42’ | òbzérôczë = 63 945 | sãdza = Jalal Jayed }} <br> ---- {{fùsbalowi mecz | etap = Mecz 76 | data = 30 czerwińca 2026, 03:00 | stadión = Estadio BBVA, Monterrey | druzyna1 = Niderlandzkô | fana1 = Netherlands | wënik1 = 1 | gòle1 = Gakpo 72’ | druzyna2 = Marokò | fana2 = Morocco | wënik2 = 1 | karné = 2-3 | gòle2 = Diop 90+1 | òbzérôczë = 68 777 | sãdza = Wilton Sampaio }} <br> ---- {{fùsbalowi mecz | etap = Mecz 77 | data = 30 czerwińca 2026, 19:00 | stadión = AT&T Stadium, Arlington | druzyna1 = Ùbrzég Słoniowëch Kłów | fana1 = Ivory Coast | wënik1 = 1 | gòle1 = Diallo 74’ | druzyna2 = Norweskô | fana2 = Norway | wënik2 = 2 | gòle2 = Nusa 39’, Haaland 86’ | òbzérôczë = 69 665 | sãdza = Jesús Valenzuela }} <br> ---- {{fùsbalowi mecz | etap = Mecz 78 | data = 30 czerwińca 2026, 23:00 | stadión = MetLife Stadium, East Rutherford | druzyna1 = Francëskô | fana1 = France | wënik1 = 3 | druzyna2 = Szwedzkô | fana2 = Sweden | wënik2 = 0 | gòle1 = Mbappé 45’, 74’; Barcola 53’ | òbzérôczë = 80 663 | sãdza = Danny Makkelie }} <br> ---- {{fùsbalowi mecz | etap = Mecz 79 | data = 1 lëpińca 2026, 03:00 | stadión = Estadio Azteca, Meksyk | druzyna1 = Meksyk | fana1 = Mexico | wënik1 = 2 | druzyna2 = Ekwador | fana2 = Ecuador | wënik2 = 0 | gòle1 = Quiñones 22’, Jiménez 31’ | òbzérôczë = 80 824 | sãdza = Slavko Vinčić }} <br> ---- {{fùsbalowi mecz | etap = Mecz 80 | data = 1 lëpińca 2026, 18:00 | stadión = Mercedes-Benz Stadium, Atlanta | druzyna1 = Anielskô | fana1 = England | wënik1 = 2 | druzyna2 = Demòkraticznô Repùblika Kònga | fana2 = Democratic Republic of the Congo | wënik2 = 1 | gòle1 = Kane 75’, 86’ | gòle2 = Cipenga 7’ | òbzérôczë = 68 239 | sãdza = Adham Makhadmeh }} <br> ---- {{fùsbalowi mecz | etap = Mecz 81 | data = 1 lëpińca 2026, 22:00 | stadión = Lumen Field, Seattle | druzyna1 = Belgiô | fana1 = Belgium | wënik1 = 3 | gòle1 = Lukaku 86’; Tielemans 89’, 120+5' | druzyna2 = Senegal | fana2 = Senegal | wënik2 = 2 | gòle2 = Diarra 24’, Sarr 51’ | òbzérôczë = 66 925 | sãdza = Said Martínez }} <br> ---- {{fùsbalowi mecz | etap = Mecz 82 | data = 2 lëpińca 2026, 02:00 | stadión = Levi’s Stadium, Santa Clara | druzyna1 = USA | fana1 = USA | wënik1 = 2 | gòle1 = Balogun 78’, Tillman 82' | druzyna2 = Bòsniô a Hercegòwina | fana2 = Bosnia and Herzegovina | wënik2 = 0 | òbzérôczë = 68 827 | sãdza = Raphael Claus }} <br> ---- {{fùsbalowi mecz | etap = Mecz 83 | data = 2 lëpińca 2026, 22:00 | stadión = SoFi Stadium, Los Angeles | druzyna1 = Szpańskô | fana1 = Spain | wënik1 = 3 | gòle1 = Oyarzabal 36', 89'; Porro 66' | druzyna2 = Aùstriackô | fana2 = Austria | wënik2 = 0 | òbzérôczë = 70 492 | sãdza = Glenn Nyberg }} <br> ---- {{fùsbalowi mecz | etap = Mecz 84 | data = 2 lëpińca 2026, 18:00 | stadión = GEHA Field at Arrowhead Stadium, Kansas City | druzyna1 = Pòrtugalskô | fana1 = Portugal | wënik1 = 2 | gòle1 = Cristiano Ronaldo 68' (k.), Gonçalo Ramos 90+4' | druzyna2 = Chòrwackô | fana2 = Croatia | wënik2 = 1 | gòle2 = Perišić 41' | òbzérôczë = 43 036 | sãdza = Espen Eskas }} <br> ---- {{fùsbalowi mecz | etap = Mecz 85 | data = 3 lëpińca 2026, 02:00 | stadión = BC Place, Vancouver | druzyna1 = Szwajcarskô | fana1 = Switzerland | wënik1 = 2 | gòle1 = Embolo 10'; Ndoye 46' | druzyna2 = Algerskô | fana2 = Algeria | wënik2 = 0 | òbzérôczë = 52 497 | sãdza = Yael Falcon Perez }} <br> ---- {{fùsbalowi mecz | etap = Mecz 86 | data = 3 lëpińca 2026, 20:00 | stadión = AT&T Stadium, Arlington | druzyna1 = Aùstraliô | fana1 = Australia | wënik1 = 1 | druzyna2 = Egipt | fana2 = Egypt | wënik2 = 1 | karné = 2-4 | gòle1 = Hany 55' | gòle2 = Ashour 13' | òbzérôczë = 70 244 | sãdza = Gustavo Tejera }} <br> ---- {{fùsbalowi mecz | etap = Mecz 87 | data = 3 lëpińca 2026, 22:00 | stadión = Hard Rock Stadium, Miami Gardens | druzyna1 = Argentińskô | fana1 = Argentina | wënik1 = 3 | gòle1 = Messi 29'; Lautaro Martínez 92'; Diney 111' | druzyna2 = Cabo Verde | fana2 = Cape Verde | wënik2 = 2 | gòle2 = D. Duarte 59'; Lopes Cabral 103' | òbzérôczë = 64 478 | sãdza = Drew Fischer }} <br> ------- {{fùsbalowi mecz | etap = Mecz 88 | data = 4 lëpińca 2026, 02:30 | stadión = GEHA Field at Arrowhead Stadium, Kansas City | druzyna1 = Kòlumbiô | fana1 = Colombia | wënik1 = 1 | gòle1 = J. Arias 14' | druzyna2 = Ghana | fana2 = Ghana | wënik2 = 0 | òbzérôczë = 69 045 | sãdza = Clément Turpin }} === 1/8 finału === {{fùsbalowi mecz | etap = Mecz 89 | data = 4 lëpińca 2026, 13:00 | stadión = NRG Stadium, Houston | druzyna1 = Kanada | fana1 = Canada | wënik1 = 0 | druzyna2 = Marokò | fana2 = Morocco | wënik2 = 3 | gòle2 = Ounahi 50’, 82’; Rahimi 90+8’ | òbzérôczë = 68 777 | sãdza = Michael Oliver }} <br> ---- {{fùsbalowi mecz | etap = Mecz 90 | data = 4 lëpińca 2026, 21:00 | stadión = MetLife Stadium, East Rutherford | druzyna1 = Paragwaj | fana1 = Paraguay | wënik1 = 0 | druzyna2 = Francëskô | fana2 = France | wënik2 = 1 | gòle2 = Mbappé 70' (k.) | òbzérôczë = 68 324 | sãdza = Ilgiz Tantashev }} <br> ---- {{fùsbalowi mecz | etap = Mecz 91 | data = 5 lëpińca 2026, 22:00 | stadión = NRG Stadium, Houston | druzyna1 = Brazylskô | fana1 = Brazil | wënik1 = 1 | gòle1 = Neymar 90+10' | druzyna2 = Norweskô | fana2 = Norway | wënik2 = 2 | gòle2 = Haaland 79', 90' | òbzérôczë = 80 663 | sãdza = Ismail Elfath }} <br> '''Bôczënk:''' jinfòrmacje zrzeszoné z pòstãpnyma zeńdzeniama mdą aktualizowóné przez całi czas. == Finał == Finał òdbëwa sã [[19 lëpińca]] 2026 rokù na stadionie ''MetLife Stadium'' w East Rutherford (aglomeracjô [[Nowi Jork|Nowégò Jorkù]]). == Òbaczë téż == {{#categorytree:Nożnô bala|hideroot=on|mode=pages}} == Przëpisë == {{Przëpisë}} == Bùtnowé lënczi == {{Commons}} {{Kòntrola}} [[Kategòrëjô:Nożnô bala]] h9qaao8ounfc9caxa5nq92t0il5qkn8 204491 204490 2026-07-05T22:26:26Z HaxelKaszëba 17492 ùprawkô 204491 wikitext text/x-wiki {{Spòrtowi Turniér infobox|gòspòdôrzë=[[Òbrôzk:Flag of Canada.svg|tło|22x22px]] [[Kanada]] [[Òbrôzk:Flag of Mexico.svg|tło|22x22px]] [[Meksyk]] [[Òbrôzk:Flag of the United States.svg|tło|22x22px]] [[Zjednóné Kraje Americzi|Zjednóné Krôje Americzi]]|data=11 czerwińca 2026|pòzwa=Mésterstwò Swiata w fùsbalë 2026<br>FIFA World Cup 2026<br>Coupe du monde de football de 2026<br>Copa Mundial de Fútbol 2026|òbrôzk=2026 FIFA World Cup emblem (with wordmark).svg|reprezentacje=48|stadiónë=16|mecze=91|gòle= 264|ùszłô=[[Mésterstwò Swiata w fùsbalë 2022|2022]]|aktualné=2026]}} '''XXIII Mésterstwò Swiata w fùsbalë 2026''' – 23. edicjô mésterstwô swiata twòrzoné przez [[FIFA]]. Turniér òdbëwô sã òd [[11 czerwińca]] do [[19 lëpińca]] 2026 rokù na stadionach w [[Kanada|Kanadze]], [[Meksyk|Meksykù]] a [[Zjednóné Kraje Americzi|Zjednónëch Krôjach]]<ref>FIFA: ''[https://www.fifa.com/en/tournaments/mens/worldcup/canadamexicousa2026 FIFA World Cup 2026 Official Website]''. [przistãp 2026-06-29].</ref>. Je to pierszi mùndial twòrzony wespólnie przez trzë krôje òrôz pierszi w historie z dzélã 48 nôrodnëch reprezentacjów. Nowi fòrmat turniéru òbjimùje 12 grëp pò szterëch karnów, z jaczich do pùcharowi fazë awansëje dwie nôlepszé reprezentacje z kòżdi grëpë òrôz òsmë nôlepszich karnów z trzecëch placów. Òznôczô to rozegranié rekordowi lëczbë 104 zeńdzeniów<ref>FIFA: ''[https://www.fifa.com/en/tournaments/mens/worldcup/canadamexicousa2026/articles/fifa-council-approves-international-match-calendar-new-format FIFA Council approves international match calendar and 104-match World Cup format]''. [przistãp 2026-06-29].</ref>. == Welënk gòspòdôrzów == [[Òbrôzk:2026 world cup.svg|mały|Wëniczi w òsoblëwëch krôjach]] 13 czerwińca 2018 rokù òb czas Kòngresu FIFA w Mòskwie dobéła wespólna kandidatura Kanadë, Meksykù a Zjednónëch Krôjów, przedobiwôjącë bédënk [[Marokò|Maroka]]<ref>FIFA: ''[https://www.fifa.com/en/tournaments/mens/worldcup/canadamexicousa2026/articles/united-2026-bid-wins-hosts-fifa-world-cup United 2026 bid voted hosts of the 2026 FIFA World Cup]''. [przistãp 2026-06-29].</ref>. Turniér je historiczny z czilu pòwodów: * pierszi mundial twòrzony przez trzë krôje; * Meksyk òstôł pierszim krôjã, jaczi gòscy mésterstwò swiata pò rôz trzecy; * Kanada pò rôz pierszi twòrzy chłopsczi mésterstwò swiata; * Zjednóné Krôje twòrzą mundial pò rôz drëdżi (pò 1994 rokù). Hewò wëniczi welowaniô: {| class="wikitable" style="text-align: center;" !Państwò !Głosë |- | align="left" |[[Òbrôzk:Flag of Canada.svg|tło|22x22px]] [[Kanada]] [[Òbrôzk:Flag of Mexico.svg|tło|22x22px]] [[Meksyk]] [[Òbrôzk:Flag of the United States.svg|tło|22x22px]] [[Zjednóné Kraje Americzi|Zjednóné Krôje Americzi]] |'''134''' |- | align="left" | [[Òbrôzk:Flag of Morocco.svg|tło|22x22px]] [[Marokò]] |65 |} == Fòrmat rozgriwków == Pò rôz pierszi w historie w finałach wëstãpùje 48 reprezentacjów. [[Òbrôzk:FIFA World Cup 2026 Draw Reception (54972845582).jpg|mały|Òficjalnô bala na mésterstwô, Trionda]] Fòrmat òbjimùje: * 12 grëp pò 4 karnów, * Kòżdé karno rozgriwô 3 grëpowé mecze, * awans zwëskiwają: ** dobiwcë grëp, ** karnë z drëdżich placów, ** òsmë nôlepszich reprezentacjów z trzecych placów, * òd pùcharowi fazë òbrzeszëwùje pùcharowi system rozpòczinôjącë sã òd 1/16 finału (Round of 32). == Gardë gòspòdôrzów == Turniér òdbëwô sã na 16 stadionach<ref>FIFA: ''[https://www.fifa.com/en/tournaments/mens/worldcup/canadamexicousa2026/articles/fifa-unveils-stellar-line-up-of-host-cities-for-2026-world-cup FIFA unveils stellar line-up of host cities for 2026 FIFA World Cup]''. [przistãp 2026-06-29].</ref>. === [[Kanada]] === * Toronto * Vancouver === [[Meksyk]] === * Meksyk * Guadalajara * Monterrey === [[Zjednóné Kraje Americzi|Zjednóné Krôje]] === * Atlanta * Boston * Dallas * Houston * Kansas City * Los Angeles * Miami * [[Nowi Jork]] / East Rutherford * Filadelfia * San Francisco Bay Area (Santa Clara) * Seattle == Grëpòwa faza == Reprezentacje òstôłë pòdzelóné na 12 grëp òznôczonëch lëterama òd A do L. Kòżdé karno rozgriwô trzë zeńdzeniô. === Grëpa A === {{#invoke:Grëpa MS|render |group=Grëpa A |1=[[File:Flag of Mexico.svg|20px]] Meksyk |1pkt=9 |1m=3 |1w=3 |1r=0 |1p=0 |1g+=6 |1g-=0 |1rg=+6 |2=[[File:Flag of South Africa.svg|20px]] Pôłniowô Afrika |2pkt=4 |2m=3 |2w=1 |2r=1 |2p=1 |2g+=2 |2g-=3 |2rg=-1 |3=[[File:Flag of South Korea.svg|20px]] Pôłniowô Korea |3pkt=3 |3m=3 |3w=1 |3r=0 |3p=2 |3g+=2 |3g-=3 |3rg=-1 |4=[[File:Flag of Czech Republic.svg|20px]] Czeskô |4pkt=1 |4m=3 |4w=0 |4r=1 |4p=2 |4g+=2 |4g-=4 |4rg=-2 }} === Grëpa B === {{#invoke:Grëpa MS|render |group=Grëpa B |advance=3 |1=[[File:Flag of Switzerland.svg|20px]] Szwajcarskô |1pkt=7 |1m=3 |1w=2 |1r=1 |1p=0 |1g+=6 |1g-=3 |1rg=+3 |2=[[File:Flag of Canada.svg|20px]] Kanada |2pkt=4 |2m=3 |2w=1 |2r=1 |2p=1 |2g+=5 |2g-=4 |2rg=+1 |3=[[File:Flag of Bosnia and Herzegovina.svg|20px]] Bòsniô a Hercegòwina |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=4 |3g-=5 |3rg=-1 |4=[[File:Flag of Qatar.svg|20px]] Katar |4pkt=1 |4m=3 |4w=0 |4r=1 |4p=2 |4g+=2 |4g-=9 |4rg=-7 }} === Grëpa C === {{#invoke:Grëpa MS|render |group=Grëpa C |1=[[File:Flag of Brazil.svg|20px]] Brazylskô |1pkt=7 |1m=3 |1w=2 |1r=1 |1p=0 |1g+=7 |1g-=1 |1rg=+6 |2=[[File:Flag of Morocco.svg|20px]] Marokò |2pkt=7 |2m=3 |2w=2 |2r=1 |2p=0 |2g+=6 |2g-=3 |2rg=+3 |3=[[File:Flag of Scotland.svg|20px]] Szkòckô |3pkt=3 |3m=3 |3w=1 |3r=0 |3p=2 |3g+=2 |3g-=5 |3rg=-3 |4=[[File:Flag of Haiti.svg|20px]] Haiti |4pkt=0 |4m=3 |4w=0 |4r=0 |4p=3 |4g+=2 |4g-=8 |4rg=-6 }} === Grëpa D === {{#invoke:Grëpa MS|render |group=Grëpa D |advance=3 |1=[[File:Flag of United States.svg|20px]] USA |1pkt=6 |1m=3 |1w=2 |1r=0 |1p=1 |1g+=8 |1g-=4 |1rg=+4 |2=[[File:Flag of Australia.svg|20px]] Aùstraliô |2pkt=4 |2m=3 |2w=1 |2r=1 |2p=1 |2g+=2 |2g-=2 |2rg=0 |3=[[File:Flag of Paraguay.svg|20px]] Paragwaj |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=3 |3g-=4 |3rg=-1 |4=[[File:Flag of Turkey.svg|20px]] Tëreckô |4pkt=3 |4m=3 |4w=1 |4r=0 |4p=2 |4g+=3 |4g-=5 |4rg=-2 }} === Grëpa E === {{#invoke:Grëpa MS|render |group=Grëpa E |advance=3 |1=[[File:Flag of Germany.svg|20px]] Niemieckô |1pkt=6 |1m=3 |1w=2 |1r=0 |1p=1 |1g+=10 |1g-=4 |1rg=+6 |2=[[File:Flag of Ivory Coast.svg|20px]] Ùbrzég Słoniowëch Kłów |2pkt=6 |2m=3 |2w=2 |2r=0 |2p=1 |2g+=4 |2g-=2 |2rg=+2 |3=[[File:Flag of Ecuador.svg|20px]] Ekwador |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=2 |3g-=2 |3rg=0 |4=[[File:Flag of Curaçao.svg|20px]] Curaçao |4pkt=1 |4m=3 |4w=0 |4r=1 |4p=2 |4g+=1 |4g-=9 |4rg=-8 }} === Grëpa F === {{#invoke:Grëpa MS|render |group=Grëpa F |advance=3 |1=[[File:Flag of Netherlands.svg|20px]] Niderlandzkô |1pkt=7 |1m=3 |1w=2 |1r=1 |1p=0 |1g+=10 |1g-=4 |1rg=+6 |2=[[File:Flag of Japan.svg|20px]] Japòńskô |2pkt=5 |2m=3 |2w=1 |2r=2 |2p=0 |2g+=7 |2g-=3 |2rg=+4 |3=[[File:Flag of Sweden.svg|20px]] Szwedzkô |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=7 |3g-=7 |3rg=0 |4=[[File:Flag of Tunisia.svg|20px]] Tunezjô |4pkt=0 |4m=3 |4w=0 |4r=0 |4p=3 |4g+=2 |4g-=12 |4rg=-10 }} === Grëpa G === {{#invoke:Grëpa MS|render |group=Grëpa G |1=[[File:Flag of Belgium.svg|20px]] Belgiô |1pkt=5 |1m=3 |1w=1 |1r=2 |1p=0 |1g+=6 |1g-=2 |1rg=+4 |2=[[File:Flag of Egypt.svg|20px]] Egipt |2pkt=5 |2m=3 |2w=1 |2r=2 |2p=0 |2g+=5 |2g-=3 |2rg=+2 |3=[[File:Flag of Iran.svg|20px]] Iran |3pkt=3 |3m=3 |3w=0 |3r=3 |3p=0 |3g+=3 |3g-=3 |3rg=0 |4=[[File:Flag of New Zealand.svg|20px]] Nowô Zelandzkô |4pkt=1 |4m=3 |4w=0 |4r=1 |4p=2 |4g+=4 |4g-=10 |4rg=-6 }} === Grëpa H === {{#invoke:Grëpa MS|render |group=Grëpa H |1=[[File:Flag of Spain.svg|20px]] Szpańskô |1pkt=7 |1m=3 |1w=2 |1r=1 |1p=0 |1g+=5 |1g-=0 |1rg=+5 |2=[[File:Flag of Cape Verde.svg|20px]] Cabo Verde |2pkt=3 |2m=3 |2w=0 |2r=3 |2p=0 |2g+=2 |2g-=2 |2rg=0 |3=[[File:Flag of Uruguay.svg|20px]] Ùrugwaj |3pkt=2 |3m=3 |3w=0 |3r=2 |3p=1 |3g+=3 |3g-=4 |3rg=-1 |4=[[File:Flag of Saudi Arabia.svg|20px]] Saùdijskô Arabiô |4pkt=2 |4m=3 |4w=0 |4r=2 |4p=1 |4g+=1 |4g-=5 |4rg=-4 }} === Grëpa I === {{#invoke:Grëpa MS|render |group=Grëpa I |advance=3 |1=[[File:Flag of France.svg|20px]] Francëskô |1pkt=9 |1m=3 |1w=3 |1r=0 |1p=0 |1g+=10 |1g-=2 |1rg=+8 |2=[[File:Flag of Norway.svg|20px]] Norweskô |2pkt=6 |2m=3 |2w=2 |2r=0 |2p=1 |2g+=8 |2g-=7 |2rg=+1 |3=[[File:Flag of Senegal.svg|20px]] Senegal |3pkt=3 |3m=3 |3w=1 |3r=0 |3p=2 |3g+=8 |3g-=6 |3rg=+2 |4=[[File:Flag of Iraq.svg|20px]] Irak |4pkt=0 |4m=3 |4w=0 |4r=0 |4p=3 |4g+=1 |4g-=12 |4rg=-11 }} === Grëpa J === {{#invoke:Grëpa MS|render |group=Grëpa J |advance=3 |1=[[File:Flag of Argentina.svg|20px]] Argentińskô |1pkt=9 |1m=3 |1w=3 |1r=0 |1p=0 |1g+=8 |1g-=1 |1rg=+7 |2=[[File:Flag of Austria.svg|20px]] Aùstriackô |2pkt=4 |2m=3 |2w=1 |2r=1 |2p=1 |2g+=7 |2g-=7 |2rg=0 |3=[[File:Flag of Algeria.svg|20px]] Algierskô |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=5 |3g-=7 |3rg=-2 |4=[[File:Flag of Jordan.svg|20px]] Jordańskô |4pkt=0 |4m=3 |4w=0 |4r=0 |4p=3 |4g+=3 |4g-=8 |4rg=-5 }} === Grëpa K === {{#invoke:Grëpa MS|render |group=Grëpa K |advance=3 |1=[[File:Flag of Colombia.svg|20px]] Kòlumbiô |1pkt=7 |1m=3 |1w=2 |1r=1 |1p=0 |1g+=4 |1g-=1 |1rg=+3 |2=[[File:Flag of Portugal.svg|20px]] Pòrtugalskô |2pkt=5 |2m=3 |2w=1 |2r=2 |2p=0 |2g+=6 |2g-=1 |2rg=+5 |3=[[File:Flag of Democratic Republic of the Congo.svg|20px]] DRK |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=4 |3g-=3 |3rg=+1 |4=[[File:Flag of Uzbekistan.svg|20px]] Ùzbekistan |4pkt=0 |4m=3 |4w=0 |4r=0 |4p=3 |4g+=2 |4g-=11 |4rg=-9 }} === Grëpa L === {{#invoke:Grëpa MS|render |group=Grëpa L |advance=3 |1=[[File:Flag of England.svg|20px]] Anielskô |1pkt=7 |1m=3 |1w=2 |1r=1 |1p=0 |1g+=6 |1g-=2 |1rg=+4 |2=[[File:Flag of Croatia.svg|20px]] Chòrwackô |2pkt=6 |2m=3 |2w=2 |2r=0 |2p=1 |2g+=5 |2g-=5 |2rg=0 |3=[[File:Flag of Ghana.svg|20px]] Ghana |3pkt=4 |3m=3 |3w=1 |3r=1 |3p=1 |3g+=2 |3g-=2 |3rg=0 |4=[[File:Flag of Panama.svg|20px]] Panama |4pkt=0 |4m=3 |4w=0 |4r=0 |4p=3 |4g+=0 |4g-=4 |4rg=-4 }} === Ranking karnów z trzecëch placów === {| class="wikitable" style="width:100%; table-layout:fixed; text-align:center; font-family:system-ui, Arial;" <tr style="background:#f8f9fa;"> <th style="width:6%;">M.</th> <th style="width:40%;">Reprezentacjô</th> <th style="width:15%;">Grëpa</th> <th style="width:9%;">Pkt</th> <th style="width:6%;">M</th> <th style="width:6%;">W</th> <th style="width:6%;">R</th> <th style="width:6%;">P</th> <th style="width:6%;">+</th> <th style="width:6%;">–</th> <th style="width:6%;">±</th> </tr> |- | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Democratic Republic of the Congo.svg|20px]] DRK | style="background:#dff6df; font-weight:bold;" | K | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | +1 |- | style="background:#dff6df; font-weight:bold;" | 2 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Ecuador.svg|20px]] Ekwador | style="background:#dff6df; font-weight:bold;" | E | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 2 | style="background:#dff6df; font-weight:bold;" | 2 | style="background:#dff6df; font-weight:bold;" | 0 |- | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Sweden.svg|20px]] Szwedzkô | style="background:#dff6df; font-weight:bold;" | F | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 7 | style="background:#dff6df; font-weight:bold;" | 7 | style="background:#dff6df; font-weight:bold;" | 0 |- | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Bosnia and Herzegovina.svg|20px]] Bòsniô a Hercegòwina | style="background:#dff6df; font-weight:bold;" | B | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 5 | style="background:#dff6df; font-weight:bold;" | −1 |- | style="background:#dff6df; font-weight:bold;" | 5 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Paraguay.svg|20px]] Paragwaj | style="background:#dff6df; font-weight:bold;" | D | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | −1 |- | style="background:#dff6df; font-weight:bold;" | 6 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Ghana.svg|20px]] Ghana | style="background:#dff6df; font-weight:bold;" | L | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 2 | style="background:#dff6df; font-weight:bold;" | 2 | style="background:#dff6df; font-weight:bold;" | 0 |- | style="background:#dff6df; font-weight:bold;" | 7 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Algeria.svg|20px]] Algierskô | style="background:#dff6df; font-weight:bold;" | J | style="background:#dff6df; font-weight:bold;" | 4 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 5 | style="background:#dff6df; font-weight:bold;" | 7 | style="background:#dff6df; font-weight:bold;" | −2 |- | style="background:#dff6df; font-weight:bold;" | 8 | style="background:#dff6df; font-weight:bold; text-align:left;" | [[File:Flag of Senegal.svg|20px]] Senegal | style="background:#dff6df; font-weight:bold;" | I | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 3 | style="background:#dff6df; font-weight:bold;" | 1 | style="background:#dff6df; font-weight:bold;" | 0 | style="background:#dff6df; font-weight:bold;" | 2 | style="background:#dff6df; font-weight:bold;" | 8 | style="background:#dff6df; font-weight:bold;" | 6 | style="background:#dff6df; font-weight:bold;" | +2 |- | 9 | style="text-align:left;" | [[File:Flag of Iran.svg|20px]] Iran | G | 3 || 3 || 0 || 3 || 0 || 3 || 3 || 0 |- | 10 | style="text-align:left;" | [[File:Flag of South Korea.svg|20px]] Pôłniowô Korea | A | 3 || 3 || 1 || 0 || 2 || 2 || 3 || −1 |- | 11 | style="text-align:left;" | [[File:Flag of Scotland.svg|20px]] Szkòckô | C | 3 || 3 || 1 || 0 || 2 || 2 || 5 || −3 |- | 12 | style="text-align:left;" | [[File:Flag of Uruguay.svg|20px]] Ùrugwaj | H | 2 || 3 || 0 || 2 || 1 || 3 || 4 || −1 |} == Pùcharowô faza == === Drôbka === {{Turniérowô drôbka | boldwinner=y | RD1= 1/16 finału | RD2= 1/8 finału | RD1-text01=29 czerwińca – [[Gillette Stadium|Foxborough]] | RD1-team01=[[File:Flag of Germany.svg|20px]] Niemieckô | RD1-score01=1 (3) | RD1-team02=[[File:Flag of Paraguay.svg|20px]] Paragwaj | RD1-score02=1 (4) | RD1-text03=28 czerwińca – [[SoFi Stadium|Inglewood]] | RD1-team03=[[File:Flag of France.svg|20px]] Francëskô | RD1-score03=3 | RD1-team04=[[File:Flag of Sweden.svg|20px]] Szwedzkô | RD1-score04=0 | RD1-text05=3 lëpińca – [[BMO Field|Toronto]] | RD1-team05=[[File:Flag of South Africa.svg|20px]] Pôłniowô Afrika | RD1-team06=[[File:Flag of Canada.svg|20px]] Kanada | RD1-score05=0 | RD1-score06=1 | RD1-text07=2 lëpińca – [[Levi’s Stadium|Santa Clara]] | RD1-team07=[[File:Flag of Netherlands.svg|20px]] Niderlandzkô | RD1-team08=[[File:Flag of Morocco.svg|20px]] Marokò | RD1-score07=1 (2) | RD1-score08=1 (3) | RD1-text09=29 czerwińca – [[NRG Stadium|Houston]] | RD1-team09=[[File:Flag of Portugal.svg|20px]] Pòrtugalskô | RD1-team10=[[File:Flag of Croatia.svg|20px]] Chòrwackô | RD1-score09=2 | RD1-score10=1 | RD1-text11=1 lëpińca – [[Estadio Azteca|Meksyk]] | RD1-team11=[[File:Flag of Spain.svg|20px]] Szpańskô | RD1-team12=[[File:Flag of Austria.svg|20px]] Aùstriackô | RD1-score11=3 | RD1-score12=0 | RD1-text13=4 lëpińca – [[Hard Rock Stadium|Miami Gardens]] | RD1-team13=[[File:Flag of United States.svg|20px]] USA | RD1-team14=[[File:Flag of Bosnia and Herzegovina.svg|20px]] Bòsniô a Hercegòwina | RD1-score13=2 | RD1-score14=0 | RD1-text15=3 lëpińca – [[BC Place Stadium|Vancouver]] | RD1-team15=[[File:Flag of Belgium.svg|20px]] Belgiô | RD1-team16=[[File:Flag of Senegal.svg|20px]] Senegal | RD1-score15=3 | RD1-score16=2 | RD1-text17= | RD1-team17=[[File:Flag of Brazil.svg|20px]] Brazylskô | RD1-team18=[[File:Flag of Japan.svg|20px]] Japòńskô | RD1-score17=2 | RD1-score18=1 | RD1-text19= | RD1-team19=[[File:Flag of Ivory Coast.svg|20px]] Ùbrzég Słoniowëch Kłów | RD1-team20=[[File:Flag of Norway.svg|20px]] Norweskô | RD1-score19=1 | RD1-score20=2 | RD1-text21= | RD1-team21=[[File:Flag of Mexico.svg|20px]] Meksyk | RD1-team22=[[File:Flag of Ecuador.svg|20px]] Ekwador | RD1-score21=2 | RD1-score22=0 | RD1-text23= | RD1-team23=[[File:Flag of England.svg|20px]] Anielskô | RD1-team24=[[File:Flag of the Democratic Republic of the Congo.svg|20px]] DRK | RD1-score23=2 | RD1-score24=1 | RD1-text25= | RD1-team25=[[File:Flag of Argentina.svg|20px]] Argentińskô | RD1-team26=[[File:Flag of Cape Verde.svg|20px]] Cabo Verde | RD1-score25=3 | RD1-score26=2 | RD1-text27= | RD1-team27=[[File:Flag of Australia.svg|20px]] Aùstraliô | RD1-team28=[[File:Flag of Egypt.svg|20px]] Egipt | RD1-score27=1 (2) | RD1-score28=1 (4) | RD1-text29= | RD1-team29=[[File:Flag of Switzerland.svg|20px]] Szwajcarskô | RD1-team30=[[File:Flag of Algeria.svg|20px]] Algierskô | RD1-score29=2 | RD1-score30=0 | RD1-text31= | RD1-team31=[[File:Flag of Colombia.svg|20px]] Kòlumbiô | RD1-team32=[[File:Flag of Ghana.svg|20px]] Ghana | RD1-score31=1 | RD1-score32=0 | RD2-text01=4 lëpińca – [[Lincoln Financial Field|Filadelfia]] | RD2-team01=[[File:Flag of Paraguay.svg|20px]] Paragwaj | RD2-team02=[[File:Flag of France.svg|20px]] Francëskô | RD2-score01=0 | RD2-score02=1 | RD2-text02=4 lëpińca – [[NRG Stadium|Houston]] | RD2-team03=[[File:Flag of Canada.svg|20px]] Kanada | RD2-team04=[[File:Flag of Morocco.svg|20px]] Marokò | RD2-score03=0 | RD2-score04=3 | RD2-text03=6 lëpińca – [[AT&T Stadium|Arlington]] | RD2-team05=[[File:Flag of Portugal.svg|20px]] Pòrtugalskô | RD2-team06=[[File:Flag of Spain.svg|20px]] Szpańskô | RD2-score05= | RD2-score06= | RD2-text04=7 lëpińca – [[CenturyLink Field|Seattle]] | RD2-team07=[[File:Flag of USA.svg|20px]] USA | RD2-team08=[[File:Flag of Belgium.svg|20px]] Belgiô | RD2-score07= | RD2-score08= | RD2-text05=5 lëpińca – [[MetLife Stadium|East Rutherford]] | RD2-team09=[[File:Flag of Brazil.svg|20px]] Brazylskô | RD2-team10=[[File:Flag of Norway.svg|20px]] Norweskô | RD2-score09=1 | RD2-score10=2 | RD2-text06=6 lëpińca – [[Estadio Azteca|Meksyk]] | RD2-team11=[[File:Flag of Mexico.svg|20px]] Meksyk | RD2-team12=[[File:Flag of England.svg|20px]] Anielskô | RD2-score11= | RD2-score12= | RD2-text07=7 lëpińca – [[Mercedes-Benz Stadium|Atlanta]] | RD2-team13=[[File:Flag of Argentina.svg|20px]] Argentińskô | RD2-team14=[[File:Flag of Egypt.svg|20px]] Egipt | RD2-score13= | RD2-score14= | RD2-text08=7 lëpińca – [[BC Place Stadium|Vancouver]] | RD2-team15=[[File:Flag of Switzerland.svg|20px]] Szwajcarskô | RD2-team16=[[File:Flag of Colombia.svg|20px]] Kòlumbiô | RD2-score15= | RD2-score16= | RD3-text01=9 lëpińca – [[Gillette Stadium|Foxborough]] | RD3-team01=[[File:Flag of Morocco.svg|20px]] Marokò | RD3-team02=[[File:Flag of France.svg|20px]] Francëskô | RD3-score01= | RD3-score02= | RD3-text02=10 lëpińca – [[SoFi Stadium|Inglewood]] | RD3-team03=Dobiwca meczu 93 | RD3-team04=Dobiwca meczu 94 | RD3-score03= | RD3-score04= | RD3-text03=11 lëpińca – [[Hard Rock Stadium|Miami Gardens]] | RD3-team05=[[File:Flag of Norway.svg|20px]] Norweskô | RD3-team06=Dobiwca meczu 92 | RD3-score05= | RD3-score06= | RD3-text04=12 lëpińca – [[Arrowhead Stadium|Kansas City]] | RD3-team07=Dobiwca meczu 95 | RD3-team08=Dobiwca meczu 96 | RD3-score07= | RD3-score08= | RD4-text01=14 lëpińca – [[AT&T Stadium|Arlington]] | RD4-team01=Dobiwca meczu 97 | RD4-team02=Dobiwca meczu 98 | RD4-score01= | RD4-score02= | RD4-text02=15 lëpińca – [[Mercedes-Benz Stadium|Atlanta]] | RD4-team03=Dobiwca meczu 99 | RD4-team04=Dobiwca meczu 100 | RD4-score03= | RD4-score04= | RD5-text01=19 lëpińca – [[MetLife Stadium|East Rutherford]] | RD5-team01=Dobiwca meczu 101 | RD5-team02=Dobiwca meczu 102 | RD5-score01= | RD5-score02= | RD5b-text01=18 lëpińca – [[Hard Rock Stadium|Miami Gardens]] | RD5b-team01=Przerżnóny meczu 101 | RD5b-team02=Przerżnóny meczu 102 | RD5b-score01= | RD5b-score02= }} === 1/16 finału === {{fùsbalowi mecz | etap = Mecz 73 | data = 28 czerwińca 2026, 21:00 | stadión = SoFi Stadium, Los Angeles | druzyna1 = Pôłniowô Afrika | fana1 = RSA | wënik1 = 0 | druzyna2 = Kanada | fana2 = Canada | wënik2 = 1 | gòle2 = Eustáquio 90+2 | òbzérôczë = 69 237 | sãdza = João Pinheiro }} <br> ---- {{fùsbalowi mecz | etap = Mecz 74 | data = 29 czerwińca 2026, 19:00 | stadión = NRG Stadium, Houston | druzyna1 = Brazylskô | fana1 = Brazil | wënik1 = 2 | druzyna2 = Japòńskô | fana2 = Japan | wënik2 = 1 | gòle1 = Casemiro 56’, Martinelli 90+5 | gòle2 = Sano 29’ | òbzérôczë = 68 777 | sãdza = Maurizio Mariani }} <br> ---- {{fùsbalowi mecz | etap = Mecz 75 | data = 29 czerwińca 2026, 22:30 | stadión = Gillette Stadium, Boston | druzyna1 = Niemieckô | fana1 = Germany | wënik1 = 1 | druzyna2 = Paragwaj | fana2 = Paraguay | wënik2 = 1 | karné = 3-4 | gòle1 = Havertz 54’ | gòle2 = Enciso 42’ | òbzérôczë = 63 945 | sãdza = Jalal Jayed }} <br> ---- {{fùsbalowi mecz | etap = Mecz 76 | data = 30 czerwińca 2026, 03:00 | stadión = Estadio BBVA, Monterrey | druzyna1 = Niderlandzkô | fana1 = Netherlands | wënik1 = 1 | gòle1 = Gakpo 72’ | druzyna2 = Marokò | fana2 = Morocco | wënik2 = 1 | karné = 2-3 | gòle2 = Diop 90+1 | òbzérôczë = 68 777 | sãdza = Wilton Sampaio }} <br> ---- {{fùsbalowi mecz | etap = Mecz 77 | data = 30 czerwińca 2026, 19:00 | stadión = AT&T Stadium, Arlington | druzyna1 = Ùbrzég Słoniowëch Kłów | fana1 = Ivory Coast | wënik1 = 1 | gòle1 = Diallo 74’ | druzyna2 = Norweskô | fana2 = Norway | wënik2 = 2 | gòle2 = Nusa 39’, Haaland 86’ | òbzérôczë = 69 665 | sãdza = Jesús Valenzuela }} <br> ---- {{fùsbalowi mecz | etap = Mecz 78 | data = 30 czerwińca 2026, 23:00 | stadión = MetLife Stadium, East Rutherford | druzyna1 = Francëskô | fana1 = France | wënik1 = 3 | druzyna2 = Szwedzkô | fana2 = Sweden | wënik2 = 0 | gòle1 = Mbappé 45’, 74’; Barcola 53’ | òbzérôczë = 80 663 | sãdza = Danny Makkelie }} <br> ---- {{fùsbalowi mecz | etap = Mecz 79 | data = 1 lëpińca 2026, 03:00 | stadión = Estadio Azteca, Meksyk | druzyna1 = Meksyk | fana1 = Mexico | wënik1 = 2 | druzyna2 = Ekwador | fana2 = Ecuador | wënik2 = 0 | gòle1 = Quiñones 22’, Jiménez 31’ | òbzérôczë = 80 824 | sãdza = Slavko Vinčić }} <br> ---- {{fùsbalowi mecz | etap = Mecz 80 | data = 1 lëpińca 2026, 18:00 | stadión = Mercedes-Benz Stadium, Atlanta | druzyna1 = Anielskô | fana1 = England | wënik1 = 2 | druzyna2 = Demòkraticznô Repùblika Kònga | fana2 = Democratic Republic of the Congo | wënik2 = 1 | gòle1 = Kane 75’, 86’ | gòle2 = Cipenga 7’ | òbzérôczë = 68 239 | sãdza = Adham Makhadmeh }} <br> ---- {{fùsbalowi mecz | etap = Mecz 81 | data = 1 lëpińca 2026, 22:00 | stadión = Lumen Field, Seattle | druzyna1 = Belgiô | fana1 = Belgium | wënik1 = 3 | gòle1 = Lukaku 86’; Tielemans 89’, 120+5' | druzyna2 = Senegal | fana2 = Senegal | wënik2 = 2 | gòle2 = Diarra 24’, Sarr 51’ | òbzérôczë = 66 925 | sãdza = Said Martínez }} <br> ---- {{fùsbalowi mecz | etap = Mecz 82 | data = 2 lëpińca 2026, 02:00 | stadión = Levi’s Stadium, Santa Clara | druzyna1 = USA | fana1 = USA | wënik1 = 2 | gòle1 = Balogun 78’, Tillman 82' | druzyna2 = Bòsniô a Hercegòwina | fana2 = Bosnia and Herzegovina | wënik2 = 0 | òbzérôczë = 68 827 | sãdza = Raphael Claus }} <br> ---- {{fùsbalowi mecz | etap = Mecz 83 | data = 2 lëpińca 2026, 22:00 | stadión = SoFi Stadium, Los Angeles | druzyna1 = Szpańskô | fana1 = Spain | wënik1 = 3 | gòle1 = Oyarzabal 36', 89'; Porro 66' | druzyna2 = Aùstriackô | fana2 = Austria | wënik2 = 0 | òbzérôczë = 70 492 | sãdza = Glenn Nyberg }} <br> ---- {{fùsbalowi mecz | etap = Mecz 84 | data = 2 lëpińca 2026, 18:00 | stadión = GEHA Field at Arrowhead Stadium, Kansas City | druzyna1 = Pòrtugalskô | fana1 = Portugal | wënik1 = 2 | gòle1 = Cristiano Ronaldo 68' (k.), Gonçalo Ramos 90+4' | druzyna2 = Chòrwackô | fana2 = Croatia | wënik2 = 1 | gòle2 = Perišić 41' | òbzérôczë = 43 036 | sãdza = Espen Eskas }} <br> ---- {{fùsbalowi mecz | etap = Mecz 85 | data = 3 lëpińca 2026, 02:00 | stadión = BC Place, Vancouver | druzyna1 = Szwajcarskô | fana1 = Switzerland | wënik1 = 2 | gòle1 = Embolo 10'; Ndoye 46' | druzyna2 = Algerskô | fana2 = Algeria | wënik2 = 0 | òbzérôczë = 52 497 | sãdza = Yael Falcon Perez }} <br> ---- {{fùsbalowi mecz | etap = Mecz 86 | data = 3 lëpińca 2026, 20:00 | stadión = AT&T Stadium, Arlington | druzyna1 = Aùstraliô | fana1 = Australia | wënik1 = 1 | druzyna2 = Egipt | fana2 = Egypt | wënik2 = 1 | karné = 2-4 | gòle1 = Hany 55' | gòle2 = Ashour 13' | òbzérôczë = 70 244 | sãdza = Gustavo Tejera }} <br> ---- {{fùsbalowi mecz | etap = Mecz 87 | data = 3 lëpińca 2026, 22:00 | stadión = Hard Rock Stadium, Miami Gardens | druzyna1 = Argentińskô | fana1 = Argentina | wënik1 = 3 | gòle1 = Messi 29'; Lautaro Martínez 92'; Diney 111' | druzyna2 = Cabo Verde | fana2 = Cape Verde | wënik2 = 2 | gòle2 = D. Duarte 59'; Lopes Cabral 103' | òbzérôczë = 64 478 | sãdza = Drew Fischer }} <br> ------- {{fùsbalowi mecz | etap = Mecz 88 | data = 4 lëpińca 2026, 02:30 | stadión = GEHA Field at Arrowhead Stadium, Kansas City | druzyna1 = Kòlumbiô | fana1 = Colombia | wënik1 = 1 | gòle1 = J. Arias 14' | druzyna2 = Ghana | fana2 = Ghana | wënik2 = 0 | òbzérôczë = 69 045 | sãdza = Clément Turpin }} === 1/8 finału === {{fùsbalowi mecz | etap = Mecz 89 | data = 4 lëpińca 2026, 13:00 | stadión = NRG Stadium, Houston | druzyna1 = Kanada | fana1 = Canada | wënik1 = 0 | druzyna2 = Marokò | fana2 = Morocco | wënik2 = 3 | gòle2 = Ounahi 50’, 82’; Rahimi 90+8’ | òbzérôczë = 68 777 | sãdza = Michael Oliver }} <br> ---- {{fùsbalowi mecz | etap = Mecz 90 | data = 4 lëpińca 2026, 21:00 | stadión = MetLife Stadium, East Rutherford | druzyna1 = Paragwaj | fana1 = Paraguay | wënik1 = 0 | druzyna2 = Francëskô | fana2 = France | wënik2 = 1 | gòle2 = Mbappé 70' (k.) | òbzérôczë = 68 324 | sãdza = Ilgiz Tantashev }} <br> ---- {{fùsbalowi mecz | etap = Mecz 91 | data = 5 lëpińca 2026, 22:00 | stadión = NRG Stadium, Houston | druzyna1 = Brazylskô | fana1 = Brazil | wënik1 = 1 | gòle1 = Neymar 90+10' | druzyna2 = Norweskô | fana2 = Norway | wënik2 = 2 | gòle2 = Haaland 79', 90' | òbzérôczë = 80 663 | sãdza = Ismail Elfath }} <br> '''Bôczënk:''' jinfòrmacje zrzeszoné z pòstãpnyma zeńdzeniama mdą aktualizowóné przez całi czas. == Finał == Finał òdbëwa sã [[19 lëpińca]] 2026 rokù na stadionie ''MetLife Stadium'' w East Rutherford (aglomeracjô [[Nowi Jork|Nowégò Jorkù]]). == Òbaczë téż == {{#categorytree:Nożnô bala|hideroot=on|mode=pages}} == Przëpisë == {{Przëpisë}} == Bùtnowé lënczi == {{Commons}} {{Kòntrola}} [[Kategòrëjô:Nożnô bala]] 32yi0coyysfnkar6ftbyxyqcji7vm49 Kania 0 13166 204481 203263 2026-07-05T19:32:32Z Rabùszniczka 19710 Usunięto przekierowanie do [[Milvus]] 204481 wikitext text/x-wiki '''Kanie''' abò '''kùlindżi''' (''Milvus'') – pòzwa na ôrt drapiérzëch ptôchów z rodzëznë [[Accipitridae|jastrzibòwatëch]] (Accipitridae), jakô w kaszëbsczim jãzekù òdnosy sã do rodzajów [[Milvus]] (do jaczégò nôleżą [[lësatô kania]], [[czôrnô kania]], [[egpiskô kania]]), ''[[Haliastur]], [[Ictinia]] (''kanialuczi'').''<ref>Ramułt, Stefan, ''Słownik języka pomorskiego, czyli kaszubskiego, s.112''</ref> rjddl5k0e4rb5h6y193q93fry49zikt 204503 204481 2026-07-06T11:23:22Z Iketsi 3254 [[Kategòrëjô:Jastrzibowaté]] 204503 wikitext text/x-wiki '''Kanie''' abò '''kùlindżi''' (''Milvus'') – pòzwa na ôrt drapiérzëch ptôchów z rodzëznë [[Accipitridae|jastrzibòwatëch]] (Accipitridae), jakô w kaszëbsczim jãzekù òdnosy sã do rodzajów [[Milvus]] (do jaczégò nôleżą [[lësatô kania]], [[czôrnô kania]], [[egpiskô kania]]), ''[[Haliastur]], [[Ictinia]] (''kanialuczi'').''<ref>Ramułt, Stefan, ''Słownik języka pomorskiego, czyli kaszubskiego, s.112''</ref> == Przëpisë == {{Przëpisë}} == Bùtnowé lënczi == {{Commons}} {{Ùzémk artikla}} {{Kòntrola}} [[Kategòrëjô:Jastrzibowaté]] qu6t7d2m8g38rti6vepjnfj8xqyorbu 204504 204503 2026-07-06T11:25:12Z Iketsi 3254 == Òbaczë téż == {{#categorytree:Jastrzibowaté|hideroot=on|mode=pages}} 204504 wikitext text/x-wiki '''Kanie''' abò '''kùlindżi''' (''Milvus'') – pòzwa na ôrt drapiérzëch ptôchów z rodzëznë [[Accipitridae|jastrzibòwatëch]] (Accipitridae), jakô w kaszëbsczim jãzekù òdnosy sã do rodzajów [[Milvus]] (do jaczégò nôleżą [[lësatô kania]], [[czôrnô kania]], [[egpiskô kania]]), ''[[Haliastur]], [[Ictinia]] (''kanialuczi'').''<ref>Ramułt, Stefan, ''Słownik języka pomorskiego, czyli kaszubskiego, s.112''</ref> == Òbaczë téż == {{#categorytree:Jastrzibowaté|hideroot=on|mode=pages}} == Przëpisë == {{Przëpisë}} == Bùtnowé lënczi == {{Commons}} {{Ùzémk artikla}} {{Kòntrola}} [[Kategòrëjô:Jastrzibowaté]] q3en9x4x4296uo7me5c01vnfjhba1ez Głos Kaszubski 0 13238 204508 204341 2026-07-06T11:49:13Z Iketsi 3254 Òbrôzk 204508 wikitext text/x-wiki {{Nôpiérwi pò kaszëbskù}} [[Òbrôzk:NDIGCZAS097591 1930 001.pdf|mały|Głos Kaszubski – R. 1, 1930, nr 1]] '''Głos Kaszubski''' – cządnik. == Òbaczë téż == {{#categorytree:Kaszëbsczé cządniczi|hideroot=on|mode=pages}} == Przëpisë == {{Przëpisë}} == Bùtnowé lënczi == {{Commons}} * https://jbc.bj.uj.edu.pl/dlibra/publication/1005385 ;Dom i Rodzina * https://jbc.bj.uj.edu.pl/dlibra/publication/1050521 {{Ùzémk artikla}} {{Kòntrola}} [[Kategòrëjô:Kaszëbsczé cządniczi]] fco75dlwtc3qtd8bmedxz3fnqs2otzc Moduł:OSM Location map 828 13245 204448 2026-07-05T14:28:58Z HaxelKaszëba 17492 Ùsôdzonô nowô starna "require('strict') local delink=require('Module:Delink').delink local getArgs = require('Module:Arguments').getArgs local p = {} local maplist={} local sgNames={} local highlightOption=false local highlightNum local visibleLinks local geoTotalPop = 0 local captionTable = '' local captionTableText = {} -- This module creates framed maps of anywhere in the world, at the required scale, and enables annotations, -- dots, shapes, lines and other ways to customise the…" 204448 Scribunto text/plain require('strict') local delink=require('Module:Delink').delink local getArgs = require('Module:Arguments').getArgs local p = {} local maplist={} local sgNames={} local highlightOption=false local highlightNum local visibleLinks local geoTotalPop = 0 local captionTable = '' local captionTableText = {} -- This module creates framed maps of anywhere in the world, at the required scale, and enables annotations, -- dots, shapes, lines and other ways to customise the area of the map being shown. It also provides a link -- to an interactive fullscreen version, which has locator dots instead of annotations and shapes. -- This is the 2025 successor module to a wiki-markup template version of 2024, which itself was a successor -- to the 'Graph'/VEGA driven template that was begun in 2016, until the Vega version was switched off in 2023. -- This module is called from template {{OSM Location map}}, which uses the same parameter formats as before. -- In addition it may at some point be possible to use a more concise parameter format using the template {{OSM Location dots}} -- In general the css output from the two formats will be identical, but the the concise version will allow bits of -- greater control over some of the settings. -- see the documentation on the two template pages for details of how to use the mapping features. -- If language customisation is needed, there are text items below that can be translated. Also see the color table -- below with details of how to add additional color names to allow localised alternatives. -- (Translating other language shape-types could be possible, but has not currently been contemplated. -- Parameter name translation would be harder but likely to be possible, ideally still retaining compatibility -- with template calls already written using English). -- -------------- the following text variables are used within the template, and can be translated as required --------------- -- -------------- you may wish to retain the English version as a comment, to keep track of the original phrase -------------- local negativeAnswer={no=1,'0'-1,off=1} local fullscreenlinktext = 'Click for interactive fullscreen map with links to nearby articles' local toggletext = 'List of numbered map items:' local termsOfUse = 'Maps: terms of use' local aboutOSM = 'About OpenStreetMaps' local yards = 'yds' local miles = 'miles' local metres = 'm' local km = 'km' local sqkm = 'sq.km' local year = 'year' local notFound = 'not found' local totalItems = 'Total' local sumOfDatapoints = 'sum of datapoints' local numberCol = 'No.' -- abbreviated for top of column of code numbers local wikidataLink = 'Wikidata page' local shapeList={} --This sets up the 'factoryDefault' shape group 0 shapeList["0"]={shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border of the shape outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items text settings for labels textSZ="11px", textCL="darkgrey", textNG="0deg", --sgf further text settings textSP="0px", textLH="120%", textOL="0px", textBG="transparent", --sgd items for dotTag text settings tagSize="10px", tagColor="white", tagSpacer="0px", tagAngle="0deg", --sge items for extension line to connect label to dot textEW="0px", textEC="darkgrey", textES="solid" } local colorList={} -- used by colorLookup to catch unsupported colors (eg 'LimeGreen'), to convert to generic version colorList['green']='hardgreen' -- it could also be added to to include alternative language equivelants, for a quick solution. colorList['red']='hardred' -- colorList ['source'] = target colorList['white']='white' -- converts any color that includes 'source' into its equivelent target colorList['blue']='hardblue' -- note, for translation you can add to this list, rather than replace it, colorList['brown']='brown' -- which would mean existing map definitions in english would also still work, alongside translated ones colorList['grey']='hardgrey' colorList['gray']='hardgrey' colorList['purple']='hardpurple' colorList['orange']='hardorange' colorList['leaf']='hardleaf' --for a more thorough translation, you can add all the variants of the colors as further CTB elements and hex values or redirects local CTB={} -- set up a table of color names (the CTB Color table index) and html hash colorhex values. CTB["paleblue"],CTB["softblue"],CTB["hardblue"],CTB["darkblue"]="#D6E1EC","#77A1CB","#4B77D6","#1c559e" CTB["palered"],CTB["softred"],CTB["hardred"],CTB["darkred"] = "#FCC6C0","#EC644B","#DB3123","#AA1205" CTB["palegreen"],CTB["softgreen"],CTB["hardgreen"],CTB["darkgreen"]= "#D2F0E5","#81AF81","#269F46","#0b7527" CTB["paleleaf"],CTB["softleaf"],CTB["hardleaf"],CTB["darkleaf"]= "#dff5c1","#b5e376","#8cc244","#679c21" CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"]= "#E8E8D6","#AAAA88","#777755","#444433" CTB["palegray"],CTB["softgray"],CTB["hardgray"],CTB["darkgray"]=CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"] CTB["palebrown"],CTB["softbrown"],CTB["hardbrown"],CTB["darkbrown"]="#FAF6ED","#CCB56C","#AD7F14","#754910" CTB["palepurple"],CTB["softpurple"],CTB["hardpurple"],CTB["darkpurple"]="#e0d1e6","#c784e0","#a029cf","#7a05a8" CTB["paleorange"],CTB["softorange"],CTB["hardorange"],CTB["darkorange"]="#ffedc2","#ffcf61","#EEB533","#e39f05" CTB["black"],CTB["white"],CTB["yellow"]="#000000","#FFFFFF","#FAF039" CTB["background"],CTB["paleground"],CTB["beigeground"]="#f9f6f2","#FEFEFA","#F5F5DC" CTB["beige"]=CTB["beigeground"] CTB["aqua"],CTB["teal"],CTB["fuchsia"] = "#00FFFF","#008080","#FF00FF" CTB["maroon"],CTB["olive"],CTB["navy"] = "#800000","#808000","#000080" CTB["lime"],CTB["limegreen"],CTB["aquamarine"] = "#00FF00","#32CD32","#7FFFD4" CTB["silver"],CTB["yellow"],CTB["orchid"] = "#800000","#FFFF00","#DA70D6" -- set up a table of predefined clip-paths local pathshape={} pathshape.squaredd = "M 19,1.25 l 0,18 -18,0 0,-18 18,0m-1,1 -16,0 0,16 16,0 0,-16m-1,1 0,14 -14,0 0,-14 14,0zm-1,1 -12,0 0,12 12,0 0,-12zm-1,1 0,10 -10,0 0,-10 10,0z" pathshape.squared = "M 18,2.5 l 0,15 -15,0 0,-15 15,0m-1,1 -13,0 0,13 13,0 0,-13zm-1,1 0,11 -11,0 0,-11 11,0z" pathshape.triangledd="M 0 20,20 20,10 0,0 20ZM1.5 19,10 1.7,18.5 19,1.5 19ZM3 18,17 18,10 3.8,3 18ZM4.5 17,10 5.4,15.4 17, 4.5,17ZM6 16,13.8 16,10 7.4z" pathshape.triangled ="M1,18 l 18,0 l -9,-18 l -9,18zm1.7,-1.1 l 7.3,-14.6 l 7.3,14.6 l -14.6, 0zm1.7,-1 l 11.0,0 l -5.5,-11 l -5.5,11z" pathshape.circledd = "M0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm0.8,0a9.2,9.2 0 1,1 18.4,0a9.2,9.2 0 1,1 -18.4,0m1,0a8.2,8.2 0 1,0 16.4,0a8.2,8.2 0 1,0 -16.4,0zm0.8,0a7.2,7.2 0 1,1 14.8,0a7.2,7.2 0 1,1 -14.8,0m1,0 a6.4,6.4 0 1,1 12.8,0a6.4,6.4 0 1,1 -12.8,0z" pathshape.circled = "M2.5,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0m0.8,0 a5,5 0 1,1 11.4,0a5,5 0 1,1 -11.4,0" pathshape.diamond = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10z" pathshape.diamondd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm1,0 l 6,8.5 l 6,-8.5 -6,-8.5 -6,8.5zm1,0 l 5,-7 5,7 -5,7 -5,-7z" pathshape.diamonddd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm0.75,0 l 6.25,9 l 6.25,-9 -6.25,-9 -6.25,9zm0.75,0 l 5.5,-8 5.5,8 -5.5,8 -5.5,-8zm0.75,0 l 4.75,7 l 4.75,-7 -4.75,-7 -4.75,7zm0.75,0 l 4,-6 4,6 -4,6 -4,-6z" pathshape.crossd = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0zM2.3,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.cross = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0z" pathshape.thincross = "M2,12 l6,0 l0,6 l4,0 l0,-6 l6,0 l0,-4 l-6,0 l0,-6 l-4,0 l0,6 l-6,0z" pathshape.fivepointstar = "M10 0 L12.245 6.91 19.511 6.91 13.633 11.18 15.878 18.09 10 13.82 4.122 18.09 6.367 11.18 0.489 6.91 7.755 6.91Z" pathshape.fivepointstard = "M10 1.5 L 11.90825 7.3735 18.08435 7.3735 13.08805 11.003 14.9963 16.8765 10 13.247 5.0037 16.8765 6.91195 11.003 1.91565 7.3735 8.09175 7.3735 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sixpointstar = "M10 0 L12.323 5.977 18.66 5 14.645 10 18.66 15 12.323 14.023 10 20 7.677 14.023 1.34 15 5.355 10 1.34 5 7.677 5.977Z" pathshape.sixpointstard = "M10 1.5 L 11.97455 6.58045 17.361 5.75 13.94825 10 17.361 14.25 11.97455 13.41955 10 18.5 8.02545 13.41955 2.639 14.25 6.05175 10 2.639 5.75 8.02545 6.58045 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sevenpointstar = "M10 0 L12.048 5.747 17.818 3.765 14.602 8.95 19.749 12.225 13.69 12.943 14.339 19.01 10 14.72 5.661 19.01 6.31 12.943 0.251 12.225 5.398 8.95 2.182 3.765 7.952 5.747Z" pathshape.sevenpointstard = "M10 1.5 L11.7408 6.38495 16.6453 4.70025 13.9117 9.1075 18.28665 11.89125 13.1365 12.50155 13.68815 17.6585 10 14.012 6.31185 17.6585 6.8635 12.50155 1.71335 11.89125 6.0883 9.1075 3.3547 4.70025 8.2592 6.38495 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.eightpointstar = "M10 0 L11.88 5.46 17.071 2.929 14.54 8.12 20 10 14.54 11.88 17.071 17.071 11.88 14.54 10 20 8.12 14.54 2.929 17.071 5.46 11.88 0 10 5.46 8.12 2.929 2.929 8.12 5.46Z" pathshape.eightpointstard = "M10 0 L10 1.5 L11.598 6.141 16.01035 3.98965 13.859 8.402 18.5 10 13.859 11.598 16.01035 16.01035 11.598 13.859 10 18.5 8.402 13.859 3.98965 16.01035 6.141 11.598 1.5 10 6.141 8.402 3.98965 3.98965 8.402 6.141ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.ring="M2.6,9.5a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.semicircle = "M0,5 C0,18.3,20,18.3,20,5 L 0,5z" pathshape.arrowhead = "M 10,10.2 l 10,2.8 -2,-3 2,-3 -10,2.8 -0.2,0.2z" pathshape.boxd=pathshape.squared pathshape.boxdd=pathshape.squaredd pathshape.ellipsed=pathshape.circled pathshape.ellipsedd=pathshape.circledd local msg={} local function debugmsg(txt) table.insert(msg,txt) end local pmsg={} local function previewMsg(txt) table.insert(pmsg,txt) end local function colorLookup(color) for c,l in pairs(colorList) do if string.find(color,c) then return l end end return color end local function getColor (color,chk) local c local opacity="100" if not color or color=='' then color='hardgrey' end if color=="transparent" then return color end if color=="background1" then color='background' end if string.byte(color,1,1)==35 and (#color == 7 or #color == 9) then c=color elseif string.byte(color,1,1)==35 and #color == 4 then c=string.sub(color,1,2)..'f'..string.sub(color,3,3)..'f'..string.sub(color,4,4)..'f' else local s=color..'1' s= s:sub(0,s:find("%d")-1) opacity=string.match(color,"%d+") if not CTB[s] then s= colorList[s] end -- check for synonyms and translations if not CTB[s] then debugmsg(mw.addWarning('color = '..color..'. The color name is not defined. Used default grey instead')) end c=CTB[s] or CTB.hardgrey end if opacity and (tonumber(opacity) < 100) and string.find(c,"#")==1 and string.len(c)==7 and opacity~="" then local hexval=string.format("%x",(math.floor(tonumber(opacity)*2.55))) c=c..hexval end return c end function p.colorvalue(frame) -- enable external access to the CTB colorTable values. usage: {{#invoke:OSM Location map|colorvalue|color=hard blue}} local c if not frame.args.color or frame.args.color=='' then c='grey' else c=string.lower(string.gsub(frame.args.color,'%s+','')) end return string.upper(string.sub(getColor(c),2)) end local function checkColors(color) local c=getColor(color,'check') local opacity =1 -- calculate colour brightness and return black or white for contrast if c=='transparent' then return c,'#000000',0 end if not (string.find(c,'#')==1) then return c,'#FFFFFF',0 end if #c>8 then opacity= tonumber('0x'..(string.sub(c,8,9)))/500 end local r=tonumber('0x'..(string.sub(c,2,3)))/255 local g=tonumber('0x'..(string.sub(c,4,5)))/255 local b=tonumber('0x'..(string.sub(c,6,7)))/255 if 0.2126 * r + 0.7152 * g + 0.0722 * b / opacity < 0.7 then return c,'#FFFFFF',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity else return c,'#000000',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity end end local function morethan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) and tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%-?%f[%d]%d[,.%d]*%f[%D]') ) b = tonumber(string.match(b, '%-?%f[%d]%d[,.%d]*%f[%D]') or '0') end return a>b end local function lessthan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) and tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%-?%f[%d]%d[,.%d]*%f[%D]')) b = tonumber(string.match(b, '%-?%f[%d]%d[,.%d]*%f[%D]') or '0') end return a<b end local function getsize(size) --size1 is between 1 and 3 values, each with px, equating to width,height,corner-rounding --eg '15px 25px 5px' (spaces are optional) or '18px'. returns three numbers local sizeval = {} for v in string.gmatch(size, "[^px]+") do table.insert(sizeval,v) end sizeval[1] = tonumber(sizeval[1]) or 13 sizeval[2] = tonumber(sizeval[2]) or sizeval[1] sizeval[3] = tonumber(sizeval[3]) or 0 return sizeval[1],sizeval[2],sizeval[3] end local function coord2text(coord) -- looks through the output from {{coord}} to find the lat and long decimal values -- and converts compass points to minus or not-minus, return with separating comma. local lat = string.match(coord,'[%.%d]+°[NS]') local lon = string.match(coord,'[%.%d]+°[EW]') local neg={N="",S="-",W="-",E=""} return neg[string.match(lat, '[NS]')]..string.match(lat,'[%.%d]+')..","..neg[string.match(lon, '[EW]')]..string.match(lon,'[%.%d]+') end local function convertCoordsTrad (row) local coords='' if row and string.find (row,'<span class="geo">') then local a,b=string.find(row,'<span class="geo">') local start=b+1 a,b=string.find(row,"</span>",b) local finish=a-1 coords=string.sub(row,start,finish) coords=string.gsub(coords,'; ',',') end return coords end local function convertCoords (row) local start,finish,lat,lon,coords,says if row then local a,b=string.find(row,"<span class=") start=a while a do -- find the final span> finish=b a,b=string.find(row,"span>",b) end if start then coords= string.sub(row,start,finish) says="" if string.find(coords,'<span class="error">') then error("coord error: badly formed coordinates",0) end coords=coord2text(coords) coords = string.sub(row,1,start-1)..coords..string.sub(row,finish+1) else coords=row end return coords else return "Nothing to see here" end end local function fillCommas(val,max) local line='' if not val then line=',' -- ensure there is some content else line = val --string.lower(string.gsub(val,"%s+","")) -- or strip spaces end if string.find(line,',') == 1 then line=' '..line end -- ensure initial comma is not skipped local _, count=string.gsub(line,",","") -- add enough subsequent commas for all entries line=line..string.rep(',',max-count) while(string.find(line,",,") ) do line=string.gsub(line,",,",", ,") --ensure string.gmatch doesn't ignore any empty items by padding with spaces end return line end local function makeLinkBox(left,top,wid,label, link) local linkBoxName='Transparent square.svg' if visibleLinks or '' =='yes' then linkBoxName='Red hollow square.svg' end local builder = mw.html.create('div') --display:inline-block; local t= string.find(label,'wikidata') -- exclude wikidata from tooltip line if t then label=string.sub(label,1,t-2) -- don't show wikidata value in tooltip if string.find(link,'wikidata') then link = '' end -- don't link on click to wikidata (if there was not another link) end builder :cssText('position:absolute;left:'..tostring(left-1-wid/2)..'px;top:'..tostring(top-1 + math.min(wid/2-12,0) - wid/2)..'px') :wikitext(string.format( '[[File:%s|%dpx|link=%s|%s]]', linkBoxName, wid+2, link, label )) return tostring(builder) end local function extractItem(row,searchItem) -- remove text following a searchItem or start of line, which might be in quote-marks to allow commas local xend,xstart=1,0 if not row then return '','' end if searchItem then xend,xstart= string.find(row or '',searchItem or 'image:') end if not xstart then return string.gsub(string.gsub(row or '',"%b\"\"", ''),"%b\'\'", '') or '','' end while row:byte(xstart+1) == 32 and xstart<#row do -- skip over any leading spaces xstart=xstart+1 end local xbyte=row:byte(xstart+1) if xbyte == 34 or xbyte == 39 then -- are they wrapped in single or double quotes xstart=xstart+1 xend=row:find(string.char(xbyte),xstart+1) else xend = row:find(',',xstart+1) -- if no quotes, we assume no commas if not xend then xend=#row+1 end end -- return residual row and extracted text return row:sub(0,xstart)..row:sub(xend), row:sub(xstart+1,xend-1) end local function itemCheck(item,ext) if not item then return nil end if not ext then ext='' end return (string.match(item,"[%.%-?%d]+") or '0')..ext end local function stripdivs(line) return string.gsub(line or '',"%b<>", ' ') end local function splitItem(item,max) -- takes a commas-sep list and returns a table of lowercase items with no spaces, or nil local r={} local x=1 item=string.lower(fillCommas(item,max)) for t in string.gmatch(item,"[^,]+") do r[x]=string.gsub(t,"%s+","") if r[x]=='' then r[x]= nil else -- residual items might have commas if x>max then r[max]=(r[max] or '')..', '..r[x] end end x=x+1 end return r end local function ParseShapeTypes (result,args,sgval) -- for use with compressed, comma-separated 'sg plus dots' parameters --[[ shape table items and default values as set at top of page shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items label text textSZ="11px", textCL="white", textNG="0deg", textAT=attributes ("bold" and/or "italic") --sgd items for dotTag tagSize="11px", tagColor="darkgrey", tagSpacer="0px", tagAngle="0deg", --sge extension line textEW="1px", textEC="darkblue", textES="solid" --sgf fx for text labels textSP="0px" textLH="100%" textOL="1px", textBG="paleground", <!--| sga = shape,Size,Color,Angle|sgb= outlineWidth,color,style eg: sga1=circle,14px,blue,0deg| sgb1=0px,dark grey,solid | sgc=textSize,color,angle,bold/italic | sgd=tagSize,Color,Spacer,Angle eg: sgc1=11px,dark grey,0deg,normal| sgd1=9px,white,0px,0deg | sge=lineWidth,color,style |sgf=textspacing,lineHeight%,outlinepx,backgroundcolor, [bold,italic] eg: sge1=0px,black, solid| sgf1=0px,120%,0px,background | dot=shape-group/lat/lon/title/dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> --]] if args["sgn"..sgval] then local sgname=string.match(args["sgn"..sgval],"(%w+)(.*)") sgNames[sgname]=sgval end local parent= args["sgp"..sgval] if parent then parent=string.match(parent,"(%w+)(.*)") local pos= string.find(parent,"%d+") if pos == 1 then parent=string.match(parent,"%d+") else parent=sgNames[parent] or '1' end end if sgval~='H' then if not parent or tonumber(parent) > tonumber(sgval) then if sgval=="1" then parent="0" else parent="1" end end end local itemTab, line, filename result[sgval]={} result[sgval].shapeType=sgval line,filename=extractItem(args['sga'..sgval] or '','image:') if sgval=='1' and not args.sga1 then line='circle,12px,blue,0deg' end -- ensure there is a parent=1 sga result[sgval].shapeFile=filename or '' -- sga= Attributes for shape itemTab=splitItem(line,4) result[sgval].shape = itemTab[1] or result[parent].shape result[sgval].shapeSize=itemTab[2] or result[parent].shapeSize result[sgval].shapeColor=itemTab[3] or result[parent].shapeColor result[sgval].shapeAngle=itemCheck(itemTab[4],'deg') or result[parent].shapeAngle -- sgb= Border outline attributes for shape itemTab=splitItem(args['sgb'..sgval],3) result[sgval].outlineWidth=itemCheck(itemTab[1],'px') or result[parent].outlineWidth result[sgval].outlineColor=itemTab[2] or result[parent].outlineColor result[sgval].outlineStyle=itemTab[3] or result[parent].outlineStyle --sgc=character attributes for label itemTab=splitItem(args['sgc'..sgval],4) result[sgval].textSZ=itemCheck(itemTab[1],'px') or result[parent].textSZ -- size of text in px result[sgval].textCL=itemTab[2] or result[parent].textCL -- colour for text result[sgval].textNG=itemCheck(itemTab[3],'deg') or result[parent].textNG -- Angle for text result[sgval].textAT=itemTab[4] or result[parent].textAT -- attributes bold, and/or italic --sgd=dotTag attributes itemTab=splitItem(args['sgd'..sgval],4) result[sgval].tagSize=itemCheck(itemTab[1],'px') or result[parent].tagSize result[sgval].tagColor=itemTab[2] or result[parent].tagColor result[sgval].tagSpacer=itemCheck(itemTab[3],'px') or result[parent].tagSpacer result[sgval].tagAngle=itemCheck(itemTab[4],'deg') or result[parent].tagAngle --sge= extension line attributes itemTab=splitItem(args['sge'..sgval],4) result[sgval].textEW=itemCheck(itemTab[1],'px') or result[parent].textEW -- width result[sgval].textEC=itemTab[2] or result[parent].textEC -- colour result[sgval].textES=itemTab[3] or result[parent].textES -- style --sgf= fx for label text itemTab=splitItem(args['sgf'..sgval],4) result[sgval].textSP=itemCheck(itemTab[1],'px') or result[parent].textSP -- spacing value for letters result[sgval].textLH=itemCheck(itemTab[2],'%') or result[parent].textLH -- Angle for text result[sgval].textOL=itemCheck(itemTab[3],'px') or result[parent].textOL -- width of text-border line result[sgval].textBG=itemTab[4] or result[parent].textBG -- color for text background return result end local function round(x,dec) -- x=number [, dec=integer] returns numeric value with upto dec decimals (all but first trailing zeros get truncated) if (dec or 0)==0 then return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5) --this avoids .0 where dec=0 end dec =10^(dec) return x>=0 and math.floor(x*dec+0.5)/dec or math.ceil(x*dec-0.5)/dec end local function maptogrid(t,r,test) --[[ converts mercator projection longitude and latitude coordinates to x and y pixel coordinates, within a frame of given size, centre coordinates and zoom level. t is a table of named indices: {lon, lat, lonbase, latbase, width, height, zoom} output is two values, x and y, rounded to r decimal places--]] local x,y x=t.width/2 + ( ((math.rad(t.lon)*6378137) - (math.rad(t.lonbase)* 6378137) ) / (156543.03*math.cos(t.latbase/180)/(2^t.zoom) ) )*(1-(0.075*(math.abs(t.latbase)/90))) y=t.height/2 + ( ( (math.log(math.tan(math.rad(t.latbase)/2+math.pi/4))*6378137) - (math.log(math.tan(math.rad(t.lat)/2+math.pi/4))*6378137) ) / (156543.03*math.cos(t.latbase/180) / (2^t.zoom) ) )*(1-(0.075*(math.abs(t.latbase)/90))) return round(x,r),round(y,r) --source: python code at https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames and https://wiki.openstreetmap.org/wiki/Mercator --[[ width and height are the size, in pixels, of the map, which will be centerd around lonbase,latbase. Method: Convert lon and lonbase to meter-offsets from coord(0,0), and subtract lonbase from lon, zoom and latbase are used to scale the resulting meter-offset to pixels, and add it to width/2. Convert lat and lat-base to meter-offsets from coord(0,0), subtract lat from latbase, scale the resulting meter-offset to pixels, add it to height/2. A correction factor '*(1-(0.075*(math.abs(t.latbase)/90) ) )' compensates for an error that seems to creep in towards the edges of the map at higher latitudes. It was identified experimentally, and ensures a dot at the edge is in the same place as if that location is positioned at the centre. Original Python code for lat,lon to x,y where 0,0 is the centre of the map print('x=',width+(((math.radians(lon1) * 6378137)- (math.radians(lonbase) * 6378137))/ (156543.03*math.cos(latbase/180)/(2**zoom))),' y=',height+(( (math.log(math.tan(math.pi / 4 + math.radians(latbase) / 2)) * 6378137)- (math.log(math.tan(math.pi / 4 + math.radians(lat1) / 2)) * 6378137))/ (156543.03*math.cos(latbase/180)/(2**zoom))) ) --]] end local function getScale(zoom, lat,magVal) if magVal and magVal>1 and magVal <=2 then zoom=zoom+(magVal-1) end local dist=(156543.03 * math.cos(math.rad(math.abs(lat))) / (2 ^ zoom))/17 local y if dist < 1 then y=(round(dist*10,1)) return tostring(y*100)..metres, tostring(round(y*109,0))..yards elseif dist <18 then y=(round(dist,0)) return tostring(y)..km, tostring(round(y*0.621371,1))..miles elseif dist <500 then y=round(dist/10) return tostring(y*10)..km, tostring(round(y*6.21371,0))..miles else y=round(dist/100) return tostring(y*100)..km, tostring(round(y*62.1371,0))..miles end end local function ParseData (args,dotval) -- for use with compressed, comma-separated 'sg plus dots' parameters -- takes a structured series of comma-separated items which get parsed as the following: -- dot(n)= (sgNumber or Name),{{coord}} or (lat,lon), (dotTag) -- dotlink(n) = single-parameter text to give wikilink and/or title used by tootlip, fullscreen dots and autocaption list -- dotlabel(n) = 'label text',pos(left,roght,top,bottom,centre,auto),(dx), (dy) pixel offsets, params, info -- dotpic(n) = single parameter wikimedia filename for an image to use in photopanel and/or fullscreen dots -- dotfeature(n)= 'mark-line' (,linewidth,style,gap) or 'photo-panel' (,image-dim,panel-width,panel-height), draws line to n-1 -- label is used if either a position and/or an x,y offset are not 0,0 ( if no label then dotTag will be put at at the x,y offset, or over the dot -- label text can be autoaligned if x,y puts it left or right of the dot, or centered if above/below) -- quote marks are not needed unless including commas within the label text -- param1 is optional items separated by spaces, and can include [nolabel nolist nomap hemisphere+1 hemisphere-1] -- info is free wikitext, to be used in the fullscreen dot box. (use dotpic to show a picture) --<!--| dotx=shape-group,[lat,lon or {{coord}} ], dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> local result={} local count=1 local row = convertCoords (args["dot"..dotval]) -- swap in any {{coord}} values so they are csv lat and lon row=fillCommas(row,4) result.code=dotval -- store the parameter name as the id code for item in string.gmatch(row,"[^,]+") do -- iterate through 'row', adding each csv item in turn, if present if count==1 then --see if it is a number or a name local pos= string.find(item,"%d+") if pos == 1 then result.group=string.lower(string.gsub(item,"%s+","")) else item=string.match(item,"(%w+)(.*)") -- ensure just a single word result.group=sgNames[item] end elseif count==2 then result.lat=tonumber(string.match(item,"[%.%-?%d]+")) or 0-- find the number, with no non-numeric stuff elseif count==3 then result.lon=tonumber(string.match(item,"[%.%-?%d]+")) or 0 elseif count==4 then result.dotTag=item:match( "^%s*(.-)%s*$" ) or "" -- dotTag allows for internal spaces, but no commas end count=count+1 end row, result.labelText= extractItem(args["dotlabel"..dotval]) result.labelText= string.gsub(result.labelText,"[%^]+","<br>") -- convert hats to line breaks local item=splitItem(row,6) result.labelPos=item[2] or 'center' result.dx=tonumber(string.match(item[3] or '0',"[%.%-?%d]+")) or 0 result.dy=tonumber(string.match(item[4] or '0',"[%.%-?%d]+")) or 0 result.param1=string.lower(item[5] or '') if string.find(result.param1,"hemisphere-1",1,true) then result.lon=result.lon-360 elseif string.find(result.param1,"hemisphere+1",1,true) then result.lon=result.lon+360 end local txt = '' if item[6] then -- ensure all info elements are included, including commas count=1 local max=6 for t1 in string.gmatch(fillCommas(row,max),"[^,]+") do if count>max then txt=txt..',' end if count>=max then txt=txt..t1 end count=count+1 end end result.info=txt result.imageName = args['dotpic'..dotval] -- Get first wikilinked item (if any) from the args.dotlink and set this plus the delinked text local testx=args["dotlink"..dotval] or '' result.dotLink=testx if testx ~= '' then testx=stripdivs(testx) result.title=delink({ testx }) local linkstart= string.find(testx,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then result.dlink=delink( { string.sub(testx,linkstart,string.find(testx,']]',1,true)+1),wikilinks='target' } ) else result.dlink='' end else result.dlink='' result.title='' end if args['dotfeature'..dotval] then local item=splitItem(args['dotfeature'..dotval],6) if (item[1] or '') =='photo-panel' then result.ppwidth= tonumber(string.match((item[3] or '110'),"%d+")) -- panel width result.ppheight= tonumber(string.match((item[4] or '48'),"%d+") ) --panel height result.photowidth=round(tonumber(string.match((item[2] or '1.3'),"[%.%-?%d]+")) * result.ppheight+1,0) -- dimension to set image size result.photoImage=result.imageName result.posType='photo-panel' elseif (item[1] or '') == 'mark-line' then local x=tonumber(dotval or '0') result.markDest=item[5] or tostring(x-1) result.mlWidth= tonumber(string.match((item[2]) or '',"%d+") or '1') result.mlStyle= item[3] or 'solid' result.mlGap=tonumber(string.match((item[4] or ''),"[%d]+") or '0') result.posType='mark-line' end --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end maplist.lon=result.lon maplist.lat=result.lat result.gridx, result.gridy = maptogrid(maplist,1) -- convert geo coords to grid xy - using values from maplist table return result end local function multiCheck (args, argName, argVal, defVal, alt) if not alt then alt='nonexistant' end if argVal=='H' and not args[argName..'H'] then argVal=highlightNum or '1' end if argVal=='' then return (args[argName] or args[alt] or (args[argName..'D']) or args[alt..'D'] or defVal) -- unnumbered args do not inherit from D or 1 else return (args[argName..argVal]) or (args[alt..argVal]) or (args[argName..'D']) or (args[alt..'D']) or (args[argName..'1']) or (args[alt..'1']) or defVal end end local function htmlTableLine (htmlTable,h1,h2,h3,h4,h5,type) if type == 'header' then if (h4 or '') ~= 'no-year' then h4='<th class="unsortable">'..h4 or ''..'</th>' else h4 = '' end if (h5 or '') ~= '' then h5='<th class="unsortable">'..h5..'</th>' else h5 = '' end return htmlTable..'<tr><th>'..(h1 or '')..'</th><th>'..(h2 or '')..'</th><th>'..(h3 or '')..'</th>'..h4..h5..'</tr>' else if (h4 or '') ~= 'no-year' then h4='<td>'..h4 or ''..'</td>' else h4 = '' end if (h5 or '') ~= '' then h5='<td>'..h5..'</td>' else h5 = '' end return htmlTable..'<tr><td>'..(h1 or '')..'</td><td>'..(h2 or '')..'</td><td>'..(h3 or '')..'</td>'..h4..h5..'</tr>' end end local function formatInt(number) if number == 0 then return notFound end local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)') int = int:reverse():gsub("(%d%d%d)", "%1,") return minus .. int:reverse():gsub("^,", "") .. fraction end local function convertToSqKm (value,punit) if string.find(punit, 'Q35852') then value = value / 100 -- convert to sq km if it's hectares elseif string.find(punit, 'Q232291') then value = value /(1/2.59) -- or this if it's square miles elseif string.find(punit, 'Q81292') then value = value / 247.1 -- or this if it's acres end return value end local function assignTradstyleShape(shapeResult,default,dotResult,args,nval) local item,itemTab local autoDotTag='' local shapeWidth,shapeHeight=0,0 local argval=nval -- to catch the unnumbered shape series local qvalue='' if argval=='0' then argval='' end if nval=='H' then shapeResult.H={} end -- Assign autovalues if a wikidata Qvalue has been supplied -- some preliminary documentation -- geo-data-type= default is P1082 (population) -- other options might include number of households (P1538) and per capita income (P10622) or land area (P2046) -- geo-data-list= this is a comma-separated list of Q values, for a manually generated list that doesn't require nymbered parameters -- geo-scalefactor = can enlarge or shrink all the dots in proportion, depending an what the map needs -- geo-region = needs to be a Qvalue of the larger administrative region that the settlements/sub-regions are in -- geo-sub-region needs to be a Pvalue, but will default to P150 (some pages use 'has parts' = P527) -- geo-data3= etc numbered Qvalue item, if used in conjunction with other shape/label parameters -- shapeD= sets default shape, which migh be n-circle to include numbers. -- geo-number-size= will give a fixed size for shape numbers. (defaults to 12 when using proportional dots) -- geo-boundaries= if set to 1 will add any Qvalues for the sub-regions to 'map-data-light'. -- show-geo-dots= to use proportional dots. Will be fixed size if 0. (set mark-sizeD=0 to have no dot at all, eg to show labels or boundaries) -- find-geo-dots=1 or 0 (needs to also include geo-region) -- label-posD = gets additional options: [off] gives no label, [on] will position lower centre, -- [on+] includes numeric values beside/below label, [on^] will always put it below. [on-] always puts values beside if args['geo-data'..argval] then -- and (args['mark-title'..argval] or '') ~= 'none' then local pvalue = args['geo-data-type'] or 'P1082' local plabel = mw.wikibase.getLabel(pvalue) if not args['geo-number-size'] then args['geo-number-size']=12 end if not args['label-posD'] then args['label-posD'] = 'on' end qvalue=args['geo-data'..argval] -- total poulation is a Qvalue wikidata page entry, or add up all the geo-data values or use a random 10000 local region = args['geo-region'] -- or mw.wikibase.getEntityIdForCurrentPage() local thisPop=0 local areatxt='' local yearVal='' if mw.wikibase.getBestStatements(qvalue,pvalue)[1] then thisPop=tonumber(mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.amount) or 0 if pvalue == 'P2046' then -- area, so convert from ha to sq km if needed (/100) thisPop = convertToSqKm(thisPop, mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.unit) end if mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers and mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers.P585 then yearVal = mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers.P585[1].datavalue.value.time if yearVal then yearVal = yearVal:sub(2,5) end -- wikidata reports an entire date and time, so extract the year end else debugmsg(mw.addWarning(mw.wikibase.getLabel(qvalue)..' ('..qvalue..') has no recorded '..plabel)) end local scalefactor=args['geo-scalefactor'] or 1 if argval=='1' and args['mark-size1'] and not args['mark-sizeD'] then args['mark-sizeD'] = args['mark-size1'] end if argval=='1' and args['label-pos1'] and not args['label-posD'] then args['label-posD'] = args['label-pos1'] end if not args['mark-size'..argval] then if args['show-geo-dots'] == '0' then args['mark-size'..argval] = args['mark-sizeD'] or '1' -- just use a 'point' for the label to attach to else if thisPop>0 then args['mark-size'..argval] = round(math.sqrt(thisPop/geoTotalPop*100) * 8 * scalefactor,0) else args['mark-size'..argval] = args['mark-sizeD'] or '1' args['shape-outline'..argval]=args['shape-outline'..argval] or args['shape-outlineH'] end end end local posTemp=string.lower(multiCheck(args,'label-pos',argval,'on')) args['label-pos'..argval] = posTemp posTemp=splitItem(posTemp,2)[1] if pvalue == 'P2046' then thisPop = round(thisPop,2) areatxt=sqkm..areatxt -- round decimals if sq km area elseif pvalue == 'P10622' then if mw.wikibase.getBestStatements(qvalue,pvalue)[1] then local punit=(mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.unit or ''):match("Q.*") -- extract Q value if punit == 'Q4917' then areatxt=' US$' elseif punit == 'Q4916' then areatxt=' euro' elseif punit == 'Q25224' then areatxt=' £GBP' else areatxt=mw.wikibase.getLabel(punit)..areatxt end end end if posTemp=='off' then else if not args['label'..argval] then args['label'..argval] = mw.wikibase.getLabel(qvalue) end local tline=posTemp:find("[%+%^%-]") if tline then local tval=posTemp:sub(tline,tline) local eline posTemp=posTemp:sub(1,tline-1) if posTemp == 'left' or posTemp == 'right' or string.find(posTemp,'east') or string.find(posTemp,'west') then if tval == '^' then eline = '^' else eline = ' ' end -- only add line break if 'forced', used for left/right elseif tval == '+' or tval == '^' then eline = '^' -- add linebreak unless 'forced' not to else eline = ' ' -- must be a dash, so no line break end args['label'..argval] = args['label'..argval]..eline..'('..formatInt(thisPop)..')' args['label-pos'..argval] = posTemp..args['label-pos'..argval]:sub(tline+1,#args['label-pos'..argval]) end end if posTemp=='on' then if argval=='1' and (not args.ldyD) then args.ldyD = args['ldy'..argval] or '0' end if not args['ldy'..argval] then args['ldy'..argval] = args.ldyD end -- ensure ldy for that dot is added to the position args['label-pos'..argval]='center' local _,br = string.gsub(args['label'..argval],'%^','') local item=splitItem(args['label-size'..argval],2) args['ldy'..argval] = (args['ldy'..argval] or 0) + ((item[1] or 11) * ((br+1)*0.6)) + ((args['geo-number-size'] or 12) / 2) end local nt='' if args['mark-title'..argval] == 'nolist' then nt='nolist,' end if not args['mark-title'..argval] or args['mark-title'..argval] == '' or args['mark-title'..argval] == 'nolist' then if mw.wikibase.getSitelink(qvalue) then args['mark-title'..argval] = nt..'[['..mw.wikibase.getSitelink(qvalue)..']]' else args['mark-title'..argval] = nt..mw.wikibase.getLabel(qvalue) end end -- If mark-title is not set locally then attempt Sitelink value, or use label if thisPop == 0 then areatxt='' end captionTableText[argval]={} captionTableText[argval][1]= args['mark-title'..argval] captionTableText[argval][2]= formatInt(thisPop) if maplist.width >= 280 then captionTableText[argval][3]= yearVal else captionTableText[argval][3]='no-year' end if args['show-q-values'] == '1' then captionTableText[argval][4]='<small>[[wikidata:'..qvalue..'|'..qvalue..']]</small>' else local nbsp = '&nbsp;' if maplist.width < 230 then nbsp = ' ' end captionTableText[argval][4]='' if captionTableText[argval][3] == 'no-year' then captionTableText[argval][2] = captionTableText[argval][2]..'<small><sup>'..nbsp..'[[wikidata:'..qvalue..'|WD]]</sup></small>' else captionTableText[argval][3] = captionTableText[argval][3]..'<small><sup>'..nbsp..'[[wikidata:'..qvalue..'|WD]]</sup></small>' end end if args['mark-title'..argval] == 'none' then -- no title else args['mark-title'..argval] = args['mark-title'..argval]..' ('..plabel..' '..formatInt(thisPop)..areatxt..')' end if not args['mark-description'..argval] then args['mark-description'..argval] = (mw.wikibase.getDescription(qvalue) or '')..'<br>' end if (not args['mark-image'..argval]) and (mw.wikibase.getBestStatements(qvalue,'P18')[1]) then args['mark-image'..argval] = mw.wikibase.getBestStatements(qvalue,'P18')[1].mainsnak.datavalue.value end item = multiCheck(args,'shape',argval,'circle') --default to circle if geo-data if not string.find(item,'svgpath,') then item=string.lower(item) end -- ensure svgpath items retain original case values else item = multiCheck(args,'shape',argval,'image') if not string.find(item,'svgpath,') then item=string.lower(item) end -- ensure svgpath items retain original case values end if string.find(item,'n-',0,true)==1 or string.find(item,'l-',0,true)==1 then autoDotTag=string.sub(item,0,1) item=string.sub(item,3) end if item == 'image' then shapeResult[nval].shape = 'image:' shapeResult[nval].shapeFile =multiCheck(args,'mark',argval,'Red pog.svg') shapeWidth=-1 else shapeResult[nval].shape = item end item= multiCheck(args,'mark-size',argval,(shapeResult[nval].shapeSize or '14px')) -- add geo-data as fallback if already assigned local a,b,c= getsize(string.gsub(string.gsub(item,',','px')..'px','pxpx','px')) if b==a and args['mark-dim'..argval] then b= b / tonumber(string.match(args['mark-dim'..argval],"[%.%-?%d]+")) end shapeHeight=b/2 item=tostring(a)..'px'..tostring(b)..'px'..tostring(c)..'px' shapeResult[nval].shapeSize= item itemTab=splitItem(multiCheck(args,'shape-color',argval,'hard red'),2) shapeResult[nval].shapeColor=itemTab[1] or 'hardred' item=itemCheck(itemTab[2],'%') -- jump through the various opacity hoops and add to color if needed if not item then item=itemCheck(args['shape-opacity'..argval],'%') end if item and item~='0%' and item~='100%' then shapeResult[nval].shapeColor=shapeResult[nval].shapeColor..item end shapeResult[nval].shapeAngle=itemCheck(multiCheck(args,'shape-angle',argval,'0'),'deg') or '0deg' if args['shape-angle'..argval] then shapeResult[nval].shapeAngleArg=true else shapeResult[nval].shapeAngleArg=false end --sort out the outline entry itemTab=splitItem(multiCheck(args,'shape-outline',argval,'transparent,0,100,solid'),4) shapeResult[nval].outlineColor=itemTab[1] or 'dark grey' shapeResult[nval].outlineWidth=itemCheck(itemTab[2],'px') or '1px' if itemTab[3] and itemTab[3]~='100' and itemTab[3]~='0' then shapeResult[nval].outlineColor=shapeResult[nval].outlineColor..itemCheck(itemTab[3],'%') end shapeResult[nval].outlineStyle=itemTab[4] or 'solid' -- label size, background, outline shapeResult[nval].textAT=args['label-attribute'..argval] or '' itemTab=splitItem( multiCheck(args,'label-size',argval,'12'),3) shapeResult[nval].textSZ=itemCheck(itemTab[1],'px') or '12px' if itemTab[2]=='outline' then shapeResult[nval].textBG=itemTab[3] or 'transparent' shapeResult[nval].textOL='1px' elseif itemTab[3]=='outline' then shapeResult[nval].textBG=itemTab[2] or 'transparent' shapeResult[nval].textOL='1px' else shapeResult[nval].textOL='0px' shapeResult[nval].textBG=itemTab[2] or 'transparent' end if getColor(shapeResult[nval].textBG)==CTB['hardgrey'] and shapeResult[nval].textBG~='hardgrey' then shapeResult[nval].textBG= 'transparent' end --label color etc itemTab=splitItem(multiCheck(args,'label-color',argval, 'darkgrey','label-colour'),2) shapeResult[nval].textCL=itemTab[1] or 'darkgrey' if itemTab[2] and itemTab[2]~='0%' and itemTab[2]~='100%' then shapeResult[nval].textCL=shapeResult[nval].textCL..itemTab[2] end shapeResult[nval].textSP=itemCheck( multiCheck(args,'label-spacing',argval,'0'),'px') -- sets letter-spacing in px shapeResult[nval].textLH=itemCheck( multiCheck(args,'label-height',argval,'120'),'%') -- sets line height, 120% default shapeResult[nval].textNG=itemCheck(multiCheck(args,'label-angle',argval,'0'),'deg') if args['label-angle'..argval] then shapeResult[nval].textNGarg=true else shapeResult[nval].textNGarg=false end --sgd=dotTag attributes shapeResult[nval].tagSize=(args['geo-number-size'] or tostring(shapeHeight*1.5))..'px' local c1,c2=checkColors(shapeResult[nval].shapeColor) shapeResult[nval].tagColor=c2 shapeResult[nval].tagSpacer='0px' shapeResult[nval].tagAngle='0deg' -- sge extension line attributes local shapePos=splitItem(multiCheck(args,'label-pos',argval,'right'),6) if shapePos[2]=='with-line' or shapePos[2]=='n-line' then shapeResult[nval].textEW=(shapePos[3] or '1')..'px' -- width shapeResult[nval].textEC=(shapePos[4] or shapeResult[nval].shapeColor or 'darkgrey') elseif shapePos[2]=='photo-panel' then shapeResult[nval].textEW='2px' -- width shapeResult[nval].textEC=shapeResult[nval].textCL else shapeResult[nval].textEW='0px' -- width shapeResult[nval].textEC='grey'-- colour end shapeResult[nval].textES='solid' if argval=='H' then return dotResult end --Assign dot values local dotItem={} dotItem.group=nval dotItem.code=nval dotItem.posType=shapePos[2] or 'nil' if (shapePos[2] or '') =='photo-panel' then dotItem.ppwidth= tonumber(string.match((shapePos[4] or '110'),"%d+")) dotItem.ppheight= tonumber(string.match((shapePos[5] or '48'),"%d+") ) dotItem.photowidth=round(tonumber(string.match((shapePos[3] or '1.3'),"[%.%-?%d]+")) * dotItem.ppheight+1,0) dotItem.photoImage=args['mark-image'..argval] --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end if (shapePos[2] or '') =='mark-line' then local x=tonumber(argval or '1') dotItem.markDest=shapePos[6] or tostring(x-1) dotItem.mlWidth= tonumber(string.match((shapePos[3] or '1'),"%d+")) dotItem.mlStyle= shapePos[4] or 'solid' dotItem.mlGap=tonumber(string.match((shapePos[5] or '0'),"[%d]+")) shapeResult[nval].textEC=shapeResult[nval].outlineColor or 'darkgrey' end if args['mark-coord'..argval] then itemTab=splitItem(convertCoordsTrad (args['mark-coord'..argval]),2) if itemTab[1] == nil or itemTab[2] == nil then debugmsg(mw.addWarning('Unable to read coordinates for mark-coord'..argval)) else dotItem.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) or 0 end else dotItem.lat=tonumber(string.match(args['mark-lat'..argval],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(args['mark-lon'..argval],"[%.%-?%d]+")) or 0 end if args['dateline'..argval] and (args['dateline'..argval]=='1' or args['dateline'..argval]=='-1') then dotItem.lon=dotItem.lon+(tonumber(args['dateline'..argval] ) *360) end maplist.lon=dotItem.lon maplist.lat=dotItem.lat dotItem.gridx, dotItem.gridy = maptogrid(maplist,1) local item=args['mark-title'..argval] or '' -- sort out the caption, wikilink and plaintext tooltip items from dotLink if item=='none' then dotItem.param1='nomap nolist' item='' end if string.find(item,'nolist') then dotItem.param1='nolist,' item = string.sub(args['mark-title'..argval],8) or args['label'..argval] end dotItem.dotLink=item if item ~= '' then item=stripdivs(item) dotItem.title=delink({item}) local linkstart= string.find(item,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then dotItem.dlink=delink({string.sub(item,linkstart,string.find(item,']]',1,true)+1),wikilinks='target'}) -- debugmsg('dlink for '..item..' is '..dotItem.dlink) else dotItem.dlink='' end else dotItem.dlink='' dotItem.title='' end if autoDotTag=='n' then item=nval elseif autoDotTag=='l' then item=string.char(64+tonumber(nval)) else item='' end dotItem.dotTag = args['numbered'..argval] or item if shapePos[2]=='n-line' and (args['label'..argval] or args['label'..argval]=='') then if dotItem.dlink =='' then item=dotItem.dotTag..' '..args['label'..argval] else item='[['..dotItem.dlink..'|'..dotItem.dotTag..']] '..args['label'..argval] end else item=(args['label'..argval] or '') end if args['labela'..argval] then item = item..'^'..args['labela'..argval] end if args['labelb'..argval] then item = item..'^'..args['labelb'..argval] end local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end dotItem.labelText = a -- convert hats to line breaks if argval=='' then item = (args['label-offset-x']) or (args.ldx) or '0' else item=args['label-offset-x'..argval] or args['ldx'..argval] or args['label-offset-xD'] or args.ldxD or args['label-offset-x1'] or args.ldx1 or '0' end dotItem.dx=tonumber(string.match(item,"[%.%-?%d]+")) if argval=='' then item = (args['label-offset-y']) or (args.ldy) or '0' else item=args['label-offset-y'..argval] or args['ldy'..argval] or args['label-offset-yD'] or args.ldyD or args['label-offset-y1'] or args.ldy1 or '0' end dotItem.dy=tonumber(string.match(item,"[%.%-?%d]+")) dotItem.labelPos=shapePos[1] if args['mark-image'..argval] then dotItem.imageName= args['mark-image'..argval] end dotItem.info=args['mark-description'..argval] or '' table.insert(dotResult,1,dotItem) -- add to start of list, so they are in reverese order for displaying -- debugmsg(dotItem.code..' = '..dotItem.lat..','..dotItem.lon..': '..dotItem.labelText..' - '..dotItem.title..', '..shapeResult[nval].shapeSize) return dotResult end local function tradstyleParseShapes(args,dotTable,dotmax) local sgNumbers,sgSortable={},{} for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['geo-data'..x] and (not args['mark-coord'..x]) and (not args['mark-lat'..x]) then -- get lat and lon values from wikidata and put them into the args.lat and args.lon for use wherever needed if (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1]) then args['mark-lat'..x] = (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1].mainsnak.datavalue.value.latitude) or '0' args['mark-lon'..x] = (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1].mainsnak.datavalue.value.longitude) or '0' local p=args['geo-data-type'] or 'P1082' if mw.wikibase.getBestStatements(args['geo-data'..x],p)[1] then local n = tonumber(mw.wikibase.getBestStatements(args['geo-data'..x],p)[1].mainsnak.datavalue.value.amount) or 0 if p == 'P2046' then -- area, so convert to sq km if needed (/100) n = convertToSqKm( n, mw.wikibase.getBestStatements(args['geo-data'..x],p)[1].mainsnak.datavalue.value.unit) end geoTotalPop = geoTotalPop + n end else debugmsg(mw.addWarning('could not find coords for '..mw.wikibase.getLabel(args['geo-data'..x])..', '..args['geo-data'..x])) end end if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) then sgNumbers[x]=x -- add it to the list end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form (needs a second table, seemingly) if args['mark-coord'] or (args['mark-lat'] and args['mark-lon']) then table.insert(sgSortable,'0') end -- add it to the end of the list local default={} default.D={} local dotResult={} for k,sgnum in pairs(sgSortable) do -- work through the sorted list, parsing each set of shapes in turn, from 1 upwards shapeList[sgnum]={} dotTable=assignTradstyleShape(shapeList,default,dotTable,args,sgnum) end dotTable=assignTradstyleShape(shapeList,default,dotTable,args,'H') -- construct an extra highlight shapeitem local unitConvert=1 local areaUnits='' if args['geo-region'] and (args['find-geo-dots'] or '1') ~= '0' then local region = args['geo-region'] -- or mw.wikibase.getEntityIdForCurrentPage() local pvalue = args['geo-data-type'] or 'P1082' local plabel = mw.wikibase.getLabel(pvalue) local subhead='' -- Assemble the subhead content, a line of text about the region as a whole if region and mw.wikibase.getBestStatements(region,pvalue)[1] then local punit=mw.wikibase.getBestStatements(region,pvalue)[1].mainsnak.datavalue.value.unit:match("Q.*") -- extract Q value if pvalue == 'P10622' then -- add other currency-based pvalues here areaUnits=mw.wikibase.getLabel(punit) end if punit == 'Q35852' then unitConvert=100 areaUnits = ' '..sqkm -- if its hectares, convert to sq km elseif punit == 'Q232291' then unitConvert = (1/2.59) areaUnits = ' '..sqkm -- or this if it's square miles elseif punit == 'Q81292' then unitConvert = 247.1 areaUnits = ' '..sqkm -- or this if it's acres elseif punit == 'Q712226' then unitConvert = 1 areaUnits = ' '..sqkm -- or this if it's already sq km elseif punit == 'Q4917' then areaUnits=' US$' -- replace long version with some shortform currencies () elseif punit == 'Q4916' then areaUnits=' euro' elseif punit == 'Q25224' then areaUnits=' £GBP' end -- if pvalue == 'P2046' then -- may need to add other instances of sq km so add in other P-items here if they need units showing -- end geoTotalPop=(mw.wikibase.getBestStatements(region,pvalue)[1].mainsnak.datavalue.value.amount) / unitConvert local yearVal if mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers then yearVal = mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P585[1].datavalue.value.time if yearVal and yearVal:sub(1,1) == '+' then yearVal = yearVal:sub(2,5) if mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459 and mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459[1] then local methodVal = mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459[1].datavalue.value.id if methodVal then yearVal=yearVal..' '..mw.wikibase.getLabel(methodVal) end end yearVal = ' ('..yearVal..')' else yearVal ='' end end subhead=totalItems..' '..mw.wikibase.getLabel(region)..' '..plabel..' ([[wikidata:'..region..']]) = '..formatInt(round(geoTotalPop),1)..areaUnits..yearVal else if pvalue == 'P2046' then areaUnits = sqkm end subhead=totalItems..' '..mw.wikibase.getLabel(region)..' '..plabel..' ('..sumOfDatapoints..') = '..formatInt(round(geoTotalPop),1)..areaUnits end if maplist.width < 350 and #plabel > 4 then plabel=string.sub((plabel or ' '),1,3)..'.' end if maplist.width < 330 then numberCol = ' ' end if maplist.width < 280 then year = 'no-year' end captionTable = subhead if (args['show-q-values'] or '') ~= '1' then captionTable = captionTable..'<div style="float:right;"><small> (WD&nbsp;=&nbsp;'..wikidataLink..')</small></div><br>' wikidataLink='' end captionTable = captionTable..'<table class="wikitable plainrowheaders sortable mw-collapsible mw-collapsed" style="width: 100%;"><caption>'..(args.toggletext or toggletext)..' </caption>' captionTable = htmlTableLine(captionTable,numberCol,mw.wikibase.getLabel(region),plabel..' '..areaUnits, year, wikidataLink,'header') end return dotTable end local function checkfortooltip (title,dx,dy,dotlabel,dlink,nolabel) -- returns tlink if available, and dlink, if needed and tshape=true if shape needed local tshape,tlink = false,"" if dlink~='' and not nolabel then tlink=dlink end if (tlink=="" or nolabel) and title~="" then tshape=true end -- tshape flags True if title is wanted for shape if (not (dx==0 or dy==0) or dotlabel=='') and title~='' then tshape=true end -- add tooltip to shape if its number has moved return tshape,tlink end local function tshift(angle) -- adjustment to place text near the centre of a triangle, shifted to allow rotation of triangle shape local x=tonumber(string.match(angle,"%-?%d+")) if x<0 then x=360+x end -- set to a single degree direction, 0 to 360 if x>359 then return 0,0 end -- shift the centre of the triangle based on rotation value if x <=40 or x>=320 then return -0.17,0 -- triangle up= -shiftv elseif x>=140 and x<=220 then return 0.17,0 --triangle down= +shiftv elseif x >220 then return 0,-0.17 --triangle left= -shifth elseif x >40 then return 0,0.22 --triangle right= +shifth end return 0,0 end local function makeTriangle(result,row,shape,outline,tlink) local w,h,r=getsize(shape.shapeSize) if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) w=w+p*2 h=h+p*2 end table.insert(result,'<div ') if tlink then table.insert(result,' title="'..row.title..'" ') end table.insert(result,'style="display:inline-block; position: absolute') if shape.shapeAngle ~= '0deg' then table.insert(result,'; transform: rotate('..shape.shapeAngle..')') end local shiftv,shifth=0,0 shiftv,shifth=tshift(shape.shapeAngle) table.insert(result,'; top: '..tostring(row.gridy-h/2+h*shiftv)..'px') table.insert(result,'; left: '..tostring(row.gridx-w/2+w*shifth)..'px; width: 0; height: 0; outline-width: 0px') table.insert(result,'; border-left: '..tostring(w/2)..'px solid transparent') table.insert(result,'; border-right: '..tostring(w/2)..'px solid transparent') if outline then -- fill with outline colour, to make a 'base layer' or shape colour table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.outlineColor).. '">') else table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.shapeColor).. '">') end table.insert(result,'</div>') end local function makeSquare(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) if shape.shape=='square' then h=w end -- squares are always square!. box can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end if r~=0 then div:css('border-radius',tostring(r).."px") end if shape.shape=='panel' then div:css('top', tostring(row.gridy).."px") div:css('left', tostring(row.gridx).."px") else div:css('top', tostring(row.gridy-h/2).."px") div:css('left', tostring(row.gridx-w/2).."px") end div:css('width', tostring(w).."px") div:css('height', tostring(h).."px") div:css('background-color', getColor(shape.shapeColor) ) div:css('color', 'inherit') table.insert(result,tostring(div)) end local function makeCircle(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding if shape.shape=='circle' then h=w end -- circles are always round. ellipse can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end div :css('top', tostring(row.gridy-h/2).."px") :css('left', tostring(row.gridx-w/2).."px") :css('width', tostring(w).."px") :css('height', tostring(h).."px") :css('border-radius', "50%") :css('background-color', getColor(shape.shapeColor) ) :css('color', 'inherit') table.insert(result,tostring(div)) end local function makeRuleA(result,row,shape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local lineV=0 if shape.shape=='rulea' then lineV=oWid*3+16 end table.insert(result,"<div style=\"display:inline-block; position: absolute") -- create a square transparent container, which will rotate line and arrow together table.insert(result,"; top:"..tostring(row.gridy - w/2).."px") table.insert(result,"; left:"..tostring(row.gridx - w/2).."px") table.insert(result,"; width:"..tostring(w).."px") table.insert(result,"; height:"..tostring(w).."px; background:transparent; color:inherit;") table.insert(result,"; transform: rotate( "..tostring(tonumber(string.match(shape.shapeAngle,"[%.%-?%d]+")) - 90).."deg);\">" ) table.insert(result,"<div style=\"display:inline-block; position: absolute") -- put the line (as a border-right) across the container table.insert(result,"; top:"..tostring(lineV).."px") table.insert(result,"; left:"..tostring((w - oWid )/2).."px; width: 0px") table.insert(result,"; height: "..tostring(w -lineV).."px") table.insert(result,"; border-right: "..shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) table.insert(result,"; background:transparent; color:inherit;\"></div>") if shape.shape=='rulea' then table.insert(result,"<div style=\"display:inline-block; position: absolute; top: 0px") --and add arrow head table.insert(result,"; left:"..tostring(w/2-( oWid/2)-oWid*0.55-2).."px; width: 0; height: 0; outline-width: 0px") table.insert(result,"; border-left: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-right: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-bottom: "..tostring(oWid*3+16).."px solid "..getColor(shape.outlineColor).."\"></div>") end table.insert(result,"</div>") end local function makeCurveA(result,row,shape) -- draw a curve with Arrow ----- local w,h=getsize(shape.shapeSize) -- = width,height local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local Angle=tonumber(string.match(shape.shapeAngle,"[%.%d]+")) table.insert(result,'<div style="position: absolute;') --set up out div, which will allow the whole to rotate table.insert(result,'top:'..tostring(row.gridy - (w + oWid*3+16)/2)..'px;') table.insert(result,'left:'..tostring(row.gridx - ( w + oWid*3+16)/2)..'px; ') table.insert(result,'width: '..tostring(w+oWid*3+16)..'px; ') table.insert(result,'height: '..tostring(w+oWid*3+16)..'px; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate( '..tostring(Angle-120)..'deg);">') else table.insert(result,'transform: rotate( '..tostring(Angle -62)..'deg);">') end table.insert(result,'<div style="position: absolute;') --set up div for the rounded corner of a rectangle table.insert(result,'border-left: '..shape.outlineWidth..' '..shape.outlineStyle..' '..getColor(shape.outlineColor)..';') if shape.shape=='curvea' then table.insert(result,'border-radius: 10000px 0 0 '..tostring(w)..'px; top:0px; left:'..tostring(w*0.25)..'px;') else table.insert(result,'border-radius: '..tostring(w)..'px 0 0 10000px;') table.insert(result,'top:'..tostring((oWid*3+16)/2+w*0.15)..'px; left:'..tostring(w*0.25)..'px;') end -- and add a triangular arrow head table.insert(result,'width: '..tostring(w)..'px; height: '..tostring(w)..'px;"></div><div style="position: absolute; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate(180deg); top:'..tostring(w-1)..'px; ') else table.insert(result,'transform: rotate(0deg); top: '..tostring(0-( ( oWid*3+16)/2)+1+( w*0.15) )..'px;') end -- reverse table.insert(result,'left:'..tostring(0-( oWid*0.6)-2+(w*0.25))..'px;') table.insert(result,'width: 0; height: 0; outline-width: 0px; border-left: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-right: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-bottom: '..tostring(oWid*3+16)..'px solid '..getColor(shape.outlineColor)..';"></div></div>') end local function makeLineTo (result,x1,y1,x2,y2,oWid, oStyle, oCol,double,shape) table.insert(result,"<div style=\"display:inline-block; position: absolute;") -- draw a line between x1,y1 and x2,y2, px-coords where 0,0 is centre of frame -- Maths calculations thanks to ES local tw if double <= 1 then tw = tonumber(string.match(oWid,"[%.%d]+"))/2 -- shift centre by half the line width else tw = tonumber(string.match(oWid,"[%.%d]+")) + (double/2) - 0.5 -- shift centre by a line width + half the gap end -- debugmsg(oWid..'+'..double..'/2='..tw) table.insert(result,"left: "..tostring(x1+( (x2-x1)/2) - (math.sqrt( ( x2-x1)^2 + (y2-y1)^2 )/2) ).."px;") table.insert(result,"top: "..tostring(y1+( ( y2-y1 )/2) - tw).."px;") table.insert(result,"width: "..tostring(math.sqrt( (x2-x1 )^2 + ( y2-y1 )^2) ).."px;") table.insert(result,"height: "..tostring(double).."px; background-color:transparent; color:inherit; ") table.insert(result,"outline-width: 0; border-bottom: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) if double>1 then table.insert(result,"border-top: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) end local lineAngle=math.atan(( y2-y1)/( x2-x1 ) )*180/math.pi -- if x1 == x2 then lineAngle=90 end table.insert(result,"transform: rotate("..lineAngle.."deg);\"></div>") -- debugmsg('text angle started as '..shape.textNG..' and lineAngle = '..lineAngle) if (lineAngle < -90) or (lineAngle >90) then lineAngle=lineAngle-180 end if shape and not shape.textNGarg then shape.textNG=tostring(round(lineAngle,0))..'deg' end -- Put in line angle for a default text angle as well -- if (y2 >= y1) and (x2 <= x1) then lineAngle=lineAngle-180 debugmsg('arrow angle corrected') end if x2 < x1 then lineAngle=lineAngle-180 end if shape and not shape.shapeAngleArg and shape.shape == 'arrowhead' then shape.shapeAngle=tostring(round(lineAngle,0))..'deg' end -- line angle for arrowhead -- debugmsg('text angle is now '..shape.textNG..'with arrow at '..lineAngle) end local function makeClipPath(result,row,shape,outline,tshape) --tshape is a flag to show if the tooltip (title=) is wanted -- return the text css div code to position and draw a shape occupying a specified clippath if string.find(shape.shape, 'svgpath,') and #shape.shape > 9 then pathshape.svgpath=string.sub(shape.shape,9) shape.shape='svgpath' end if not pathshape[shape.shape] then debugmsg(mw.addWarning('shape'..row.code..' = '..shape.shape..'. The shape name is not defined')) return end local w,h,r=getsize(shape.shapeSize) if shape.shape == 'arrowhead' then -- enlarge arrowheads, as they only go to the middle of the notional shape-size w=w*2.5 h=h*2.5 end if string.match(shape.shape,"circle") or string.match(shape.shape,"square") then h=w end -- use ellipse and box for stretched shapes local shifth,shiftv = 0,0 if string.match(shape.shape,"triangle") then shiftv,shifth =tshift(shape.shapeAngle) end if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) or 0 w=w+p*2 h=h+p*2 end table.insert(result,"<div ") if tshape then -- Add tooltip if needed table.insert(result," title=\""..row.title.."\" ") end table.insert(result,"style=\"display:inline-block; position: absolute; background-color:") if outline then table.insert(result,getColor(shape.outlineColor)) -- fill with outline colour, to make a 'base layer' else table.insert(result,getColor(shape.shapeColor)) end table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;"..pathshape[shape.shape].."&#39;) ") -- adds the required clippath data from the table of pathshape string literals table.insert(result,"; top:"..tostring(row.gridy - 10 + h*shiftv).."px") table.insert(result,"; left:"..tostring(row.gridx - 10 + w*shifth).."px") table.insert(result,"; width:20px") -- needs to be a path within a 20px20px box, and then rescales using size values to match other shape sizes table.insert(result,"; height:20px; transform:") if shape.shapeAngle ~= "0deg" then table.insert(result," rotate("..shape.shapeAngle..")") end table.insert(result," scale("..tostring(w/16)..", "..tostring(h/16)..")") table.insert(result,"\"></div>") end local function makeImage(result,row,shape) local w,h,r=getsize(shape.shapeSize) local image=shape.shapeFile if not image or image=='' then image='Red pog.svg' end local imagediv=mw.html.create ('div') imagediv:css('position', "absolute") if shape.shapeAngle ~= "0deg" then imagediv:css('transform',"rotate("..shape.shapeAngle..")") end imagediv :css('top', (row.gridy+2 + math.min(h/2-12,0) - h/2).."px") --File seems to adjust pos for small images :css('left', (row.gridx-1-w/2).."px") :css('background-color', "transparent" ) :css('color','inherit') :wikitext('[[file:'..image..'|'..tostring(w+2)..'px|alt='..(row.title or '')..'|link=]]') table.insert(result,tostring(imagediv)) end local function makePhotoPanel(result,row,shape) local h=row.ppheight table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;' ) table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') table.insert(result, 'width: '..tostring(row.ppwidth)..'px; height: '..tostring(h)..'px; border-radius: 2px; color:inherit;') table.insert(result, 'background-color: #E8E8D6; outline: 2px solid '..getColor(shape.textCL)..'; box-shadow: 2px 2px 4px #33203335;"></div>') if row.photoImage and row.photowidth >0 then table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;') if row.labelPos=='left' or string.find(row.labelPos,'west') then table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') row.dx=row.dx+row.photowidth/2 else table.insert(result, 'left: '..tostring(row.gridx+row.dx + (row.ppwidth-row.photowidth) - row.ppwidth/2)..'px;') row.dx=row.dx-row.photowidth/2 end table.insert(result, 'background-color:transparent; color:inherit; border-radius: 2px;">') table.insert(result,'[[File:'..row.photoImage..'|x'..tostring(h)..'px|File:'..row.photoImage..']]</div>') row.labelPos='center' end end local function makePanelText(result, row, shape) local w,h,r=getsize(shape.shapeSize) local ty=tonumber(string.match(shape.textSZ or '11',"%d+") ) table.insert(result,'<div style="position:absolute; line-height: 120%; font-size: '..shape.textSZ..'; color:'..getColor(shape.textCL)..';') if row.labelPos == 'left' or string.find(row.labelPos,'west') then table.insert(result,'top: '..tostring(row.dy+row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+3)..'px; text-align: left; width:'..tostring(w)..'px;') elseif row.labelPos == 'right' or string.find(row.labelPos,'east') then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+w-3)..'px; text-align: right; width: max-content; transform: translateX(-100%);') elseif row.labelPos == 'top' or row.labelPos== 'north' then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') elseif row.labelPos == 'bottom' or row.labelPos=='south' then local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*1.1 table.insert(result,'top: '..tostring(row.gridy+w-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') else -- center or centre local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*0.6 table.insert(result,'top: '..tostring(row.gridy+(h/2)-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') end if shape.textSP and shape.textSP ~='0px' then table.insert(result,"letter-spacing: "..shape.textSP..';') end if shape.textLH and shape.textLH ~='120%' then table.insert(result,"line-height: "..shape.textLH..';') end table.insert(result,"vertical-align: middle;\">"..row.labelText.."</div>") end local function makeTextItem(result, row, shape, align, tlink, textItem, dotItem) local w,h,r=getsize(shape.shapeSize) table.insert(result,"<div ") if row.title ~= "" then table.insert(result," title=\""..row.title.."\" ") end local ty,bry,linkoffset = 0,0,0 local compy=0 local lh=tonumber(string.match(shape.textLH or '120',"%d+"))/100 if dotItem==1 or (dotItem==2 and row.posType~='n-line') then -- if there is a dotTag in the middle of the shape then use the tag settings ty=tonumber(string.match(shape.tagSize,"%d+")) or 0 table.insert(result,"style=\"position:absolute; line-height: 120%; top: "..tostring(row.gridy-ty*0.62)) table.insert(result,"px; left: "..tostring(row.gridx).."px; width: fit-content; ") table.insert(result,"text-align: center; color: "..getColor(shape.tagColor).."; background-color: transparent;") local trf="" if shape.tagAngle ~="0deg" then trf=" rotate("..shape.tagAngle..")" end table.insert(result, "transform: translateX(-50%)"..trf.."; font-size: "..shape.tagSize..";") if shape.tagSpacer~='0px' then table.insert(result, "letter-spacing:"..shape.tagSpacer..";") end else -- or add tfx settings for left, right or center align, colors, backgrounds, border-outline table.insert(result,'style="position:absolute; ') if dotItem==2 then -- dotTag is out at x,y so 85% ty=tonumber(string.match(shape.tagSize or '0',"%d+")) table.insert(result,'font-size: '..shape.tagSize..'; padding:0px 2px;line-height: 85%; top: '..tostring(row.dy+row.gridy-ty*0.52)) else -- it is labelText, so use textLH or 120% ty=tonumber(string.match(shape.textSZ or '11',"%d+")) if row.labelPos=='northwest' or row.labelPos=='northeast' then compy=-ty elseif row.labelPos=='southeast' or row.labelPos=='southwest' then compy=ty/2 end if row.labelPos and not (row.labelPos== 'auto' or row.labelPos=='') then bry=(select(2, string.gsub(row.labelText,"<br>", ""))*lh) -- is it a multiline text? expand by line-height /120%? if row.posType == 'with-line' then bry=0 end if row.labelPos=='bottom' or row.labelPos == 'south' then bry = 0 -- and shift by none, all or half elseif row.labelPos=='top' or row.labelPos == 'north' then if shape.shape=='image:' then bry= 1 + math.min(w/2-10,0)-bry*ty else bry= -bry*ty+2 end else bry=-bry*(ty/2 * lh) end end if row.posType == 'photo-panel' then bry=bry+3 end table.insert(result,'font-size: '..shape.textSZ..'; padding:0px 3px;line-height: '..shape.textLH..'; ') table.insert(result,'top: '..tostring(row.dy+row.gridy+compy+bry-ty*lh/2)) end table.insert(result,"px; left: "..tostring(row.dx+row.gridx).."px; color: "..getColor(shape.textCL).."; ") table.insert(result,"width: max-content; ") local trf="" if shape.textNG ~="0deg" then trf="rotate("..shape.textNG..")" end if shape.textOL~="0px" then table.insert(result,"background-color: "..getColor(shape.textBG).."; ") table.insert(result,"border: "..shape.textOL.." solid "..getColor(shape.textCL).."; border-radius:6px;") else table.insert(result,"background-color: transparent;") end if row.labelPos=="right" or string.find(row.labelPos,'east') then table.insert(result,"text-align: left; ") linkoffset=w if shape.textNG ~="0px" then table.insert(result,"transform-origin: left; transform: rotate("..shape.textNG.."); ") end elseif row.labelPos=="left" or string.find(row.labelPos,'west') then if shape.textNG ~="0px" then table.insert(result,"transform-origin: right;") end linkoffset=-w table.insert(result,"text-align: right; transform: translateX(-100%) "..trf.."; ") else table.insert(result,"text-align: center; transform: translateX(-50%) "..trf.."; ") end table.insert(result,'font-weight: normal; line-height: '..shape.textLH..'; letter-spacing:'..shape.textSP..'; vertical-align: bottom;') end if string.find(shape.textAT or '','bold') then textItem='<b>'..textItem..'</b>' end if string.find(shape.textAT or '','italic') then textItem='<i>'..textItem..'</i>' end if shape.textOL=='0px' and shape.textBG~='transparent' and dotItem==0 then table.insert(result,'\"><span style=\"background-color: '..getColor(shape.textBG)..'; color:inherit;\">'..textItem..'</span></div>') else table.insert(result,"\">"..textItem.."</div>") end if tlink~='' and not string.match(row.param1 or "","nolink") then table.insert(result,makeLinkBox(row.gridx+row.dx+linkoffset, row.gridy+row.dy+bry, 16, row.dotTag..' '..row.title,tlink)) end end local function getshapetable(row,shape) -- Construct CSS divs for a dot from shape and map data local result={} local w,h,rndg=getsize(shape.shapeSize) local tshape,tlink=checkfortooltip(row.title,row.dx,row.dy,row.dotTag,row.dlink,string.match(row.param1 or "","nolink") ) local align=row.labelPos or '' local offsetx,offsety=0,0 local ty=tonumber(string.match((shape.tagSize or 9),"%d+" )) if row.labelText and row.labelText~='' then ty=tonumber(string.match((shape.textSZ or 11),"%d+" )) else align='center' end -- it is just for dotTag, so justify center -- identify align value and extend offsets local widthzone,heightzone = (w/2)+1,(h/2)+1 local theta,r = math.deg( math.atan2(row.dy, row.dx)) , math.sqrt(row.dx^2 + row.dy^2) if align=='auto' or align=='' then if (theta < -112 or theta > 112) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "left" offsetx=1 elseif (theta > -68 and theta < 68) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "right" offsetx=-1 elseif theta <0 then offsety=ty/2-1 -- bottom else offsety=0-ty/2+1 -- top end elseif align=='left' or string.find(align,'west') then row.dx=row.dx - w/2 widthzone=4 offsetx=-1 elseif align=='right' or string.find(align,'east') then row.dx=row.dx + w/2 widthzone=4 offsetx=1 elseif align=='top' or align=='north' then if string.find(shape.shape,'curve') then row.dy=row.dy-(h/2)-12 heightzone=4 offsety=ty/2-1 else row.dy=row.dy - h-2 heightzone=4 offsety=ty/2-1 end elseif align=='bottom' or align=='south' then if string.find(shape.shape,'curve') then row.dy=row.dy+(h/2) heightzone=4 offsety=ty/2-1 else row.dy=row.dy + h+2 heightzone=4 offsety=0-ty/2-1 end end if shape.textEW ~= "0px" and not string.match(row.param1 or "","noline") then -- debugmsg('r-(w/2) > widthzone r-w/2='..tostring(r-(w/2))..', widthzone='..widthzone..', label='..(row.labelText or '')..', postype='..(row.posType or '')) makeLineTo(result, row.gridx+1, row.gridy-1, row.gridx+row.dx+offsetx, row.gridy+row.dy+offsety, shape.textEW, shape.textES,shape.textEC,1) -- debugmsg('n-line for '..row.code..':'..tostring(row.gridx+1)..', '..tostring(row.gridy-1)..', '..tostring(row.gridx+row.dx+offsetx)..', '..tostring(row.gridy+row.dy+offsety)..', with width '..tostring(shape.textEW)..'px '..(shape.textES or 'no style')..' and color '..shape.textEC) end if row.posType and row.posType=='mark-line' then if row.gridx2 and row.gridy2 then makeLineTo(result, row.gridx, row.gridy, row.gridx2, row.gridy2, tostring(row.mlWidth)..'px', row.mlStyle, getColor(shape.textEC),row.mlGap,shape) -- debugmsg('mark-line line drawn from '..row.code..' with width '..tostring(row.mlWidth)..'px '..(row.mlStyle or 'no style')..' and color '..getColor(shape.textEC)) end end if w ~= 0 then if shape.shape=='itriangle' then shape.shape='triangle' shape.shapeSize=tostring(w)..'px'..tostring(w/2)..'px' end if shape.shape=="triangle" then if shape.outlineWidth ~= "0px" then makeTriangle(result,row,shape,true,false) -- larger triangle to give the outline, if required end makeTriangle(result,row,shape,false,tshape) -- smaller triangle to fit over the top elseif shape.shape=="square" or shape.shape=="box" or shape.shape=='panel' then makeSquare(result,row,shape,tshape) elseif shape.shape=="circle" or shape.shape=="ellipse" then makeCircle(result,row,shape,tshape) elseif string.find(shape.shape,'image:')==1 then makeImage(result,row,shape) elseif shape.shape=="rulea" or shape.shape=='rule' then makeRuleA(result,row,shape) elseif shape.shape=="curvea" or shape.shape=="curvec" then makeCurveA(result,row,shape) else -- use a pathshape clipPath if shape.outlineWidth ~= "0px" then makeClipPath(result,row,shape,true,false) -- larger path-shape to give the outline, if required end makeClipPath(result,row,shape,false,tshape) end end if row.ppwidth and row.ppwidth>0 then makePhotoPanel(result,row,shape) end if shape.shape=='panel' and row.labelText then makePanelText(result, row, shape) else if row.dotTag and row.dotTag ~= "" then -- there is a dotTag if (row.dx==0 and row.dy==0) and w>0 then -- it is on the dot so if dotsize is not 0 any label is ignored makeTextItem(result, row, shape, align, tlink, '<b>'..row.dotTag..'</b>', 1) else if row.labelText and row.labelText~='' then -- tag and label both used makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.labelText, 0) else makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.dotTag, 2) -- tag is ouside the dot end end else if (row.labelText and row.labelText~='') then -- just the label. No tag makeTextItem(result, row, shape, align, tlink, row.labelText, 0) end end end if tlink and tlink~='' then table.insert(result,makeLinkBox(row.gridx, row.gridy,w+3,row.dotTag..' '..row.title,tlink)) end return table.concat(result) end local function getmapframecontent(args,use) local result, comma= {}, '' local propertyTable= {} local color=splitItem(string.lower( args['map-data-color'] or 'dark orange'), 2) if use == 'basemap' then table.insert(result, '[') propertyTable.stroke = "#000000" propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') if color[2] then propertyTable['stroke-opacity'] = tonumber(color[2] or '20')/100 end else propertyTable.title = args['map-data-text'] or args['map-data'] or '' propertyTable.stroke = getColor(color[1]) propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') propertyTable['stroke-opacity'] = tonumber(color[2] or '100')/100 end if args['map-data-inverse'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geomask', ids = args['map-data-inverse'], properties = {title = (args['map-data-text'] or ''), stroke = '#555555', fill = '#555555', ['fill-opacity'] = 0.1, ['stroke-width'] = 1, ['stroke-opacity'] = 0.5 } } table.insert(result, mapJson) comma=', ' end if args['map-data-heavy'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-heavy'], properties = { title = args['map-data-heavy'], stroke = propertyTable.stroke, ['stroke-width'] = 9, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.2 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data-light'] and args['map-data-light'] ~= '' then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-light'], properties = { title = args['map-data-light'], stroke = propertyTable.stroke, ['stroke-width'] = 3, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.4 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data'] then propertyTable['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.2 local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data'], properties = propertyTable } table.insert(result, comma .. mapJson) comma=', ' end if args['map-wdqs'] then -- inserts a SPARQL wdqs query to find geopoints, geoshapes etc from wikidata local mapSparql = mw.text.jsonEncode{ type = 'ExternalData', service = args['map-wdqs-type'] or 'geopoint', query = args['map-wdqs'], } table.insert(result, comma .. mapSparql) -- debugmsg('mapSparql = ' .. mapSparql) comma=', ' end if args['map-raw'] then table.insert(result, comma .. args['map-raw']) end if use=='basemap' then table.insert(result,']') end return table.concat(result) end --eg | minilocator=filename,bottom right,132px153px, 38%,60%, 22px local function makeLocatorMap (args, result) local miniFile,pos,itemlist,miniW,miniH, miniX,miniY,miniBox, miniBH if args['mini-locator'] then pos,miniFile=extractItem(args['mini-locator']) -- first item is filename, in quotes if it includes commas itemlist=splitItem(pos,5) -- put items in a table filename removed, position,WpxHpx, x%y%,box pos=itemlist[2] or 'right' miniW,miniH=getsize(itemlist[3]) miniX=tonumber(string.match(itemlist[4] or '0','[%d]+'))*miniW/100 miniY=tonumber(string.match(itemlist[5] or '0','[%d]+'))*miniH/100 miniBox=tonumber(string.match(itemlist[6] or '0','[%d]+')) miniBox=miniBox*miniW/100 miniBH=miniBox * maplist.height/maplist.width elseif args['mini-file'] then miniFile = args['mini-file'] pos=string.lower(args.minimap or 'right') if pos=='file' then pos='right' end miniW,miniH = tonumber(args['mini-width'] or 60), tonumber(args['mini-height'] or 60) -- find top left corner of locator miniBox,miniBH=tonumber(args['minimap-boxwidth'] or '0'),0 -- firm up pos offsets for dot (with % or not) and boxsize if any, miniX, miniY=args['minipog-gx'],args['minipog-gy'] if not miniX then miniX=tonumber(args['minipog-x'] or '0') else miniX=tonumber(miniX)*tonumber(miniW)/100 end if not miniY then miniY=tonumber(args['minipog-y'] or '0') else miniY=tonumber(miniY)*tonumber(miniH)/100 end if args['minipog-gx'] then miniBox=miniBox*miniW/100 end miniBH=miniBox * maplist.height/maplist.width else return end local miniTop,miniLeft=0,1 if not string.find(pos,'top') then --only use top left, as link box is in top right or put bottom left or right miniTop=maplist.height+2-miniH if string.find(pos,'right') then miniTop=miniTop-15 -- to avoid (c) line miniLeft=maplist.width-1-miniW end end table.insert(result,'<div style="position: absolute; outline-width: 1px; outline-style: solid; outline-color: white;') table.insert(result,'top: '..tostring(miniTop)..'px; left:'..tostring(miniLeft)..'px; width:'..tostring(miniW)..'px;' ) table.insert(result,'background-color:transparent; color:inherit;">[[File:'..miniFile..'|'..tostring(miniW)..'px|File:'..miniFile..']]</div>') if miniX and miniX>0 then if args['minipog-y'] then miniY=miniY/1.04 end if args['minipog-x'] then miniX=miniX/1.04 end if miniBox<1 then table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-3-6)..'px; left:'..tostring(miniX+miniLeft-3)..'px;') table.insert(result, 'width: 6px; background-color:transparent; color:inherit;">[[File:Red pog.svg|6px|link=]]</div>') else table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-miniBH/2)..'px; left:'..tostring(miniX+miniLeft-miniBox/2)..'px;') table.insert(result, 'width: '..tostring(miniBox)..'px; height:'..tostring(miniBH)..'px; outline:1px solid #AA1205; background-color:#AA120522; color:inherit;"></div>') end end end local function makeArcText(args,result,nval) local items, itemlist='',{} local arcText='' if args['arc'..nval] then items=convertCoords (args['arc'..nval]) items,arcText=extractItem(items) -- first item is text, in quotes if it includes commas itemlist=splitItem(items,9) -- put items in a table: text, lat,lon,size,color,angle,gap,radius,ellipse if itemlist[4]=='' then itemlist[4]='12' end itemlist[4]=string.match((itemlist[4] or '12'),"[%.%-?%d]+") if itemlist[5]=='' then itemlist[5]='grey' end itemlist[6]=string.match((itemlist[6] or '0'),"[%.%-?%d]+") itemlist[7]=string.match((itemlist[7] or '0'),"[%.%-?%d]+") itemlist[8]=string.match((itemlist[8] or '0'),"[%.%-?%d]+") end if args['arc-coord'..nval] then local itemTab=splitItem(convertCoordsTrad (args['arc-coord'..nval]),2) maplist.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) maplist.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) else maplist.lat=tonumber(string.match(args['arc-lat'..nval] or itemlist[2] or '0',"[%.%-?%d]+")) maplist.lon=tonumber(string.match(args['arc-lon'..nval] or itemlist[3] or '0',"[%.%-?%d]+")) end local arcX,arcY=maptogrid(maplist,6) arcText = args['arc-text'..nval] or arcText local fontSize =tonumber(args['arc-text-size'..nval] or itemlist[4] or '12') local textColor=getColor(string.gsub(args['arc-text-color'..nval] or itemlist[5] or 'grey','[%s]+','') ) local arcAngle= tonumber((args['arc-angle'..nval]) or (itemlist[6]) or '45')-90 local arcRadius =tonumber(args['arc-radius'..nval] or itemlist[8] or '0.05') local arcGap = tonumber(args['arc-gap'..nval] or itemlist[7] or '1')* ( ( math.sin(8-math.rad(arcRadius))^8 )+0.4 )*( ( fontSize+6 )/15 ) arcRadius=450*arcRadius*0.75 local ellipseFactor=tonumber(args['ellipse-factor'..nval] or itemlist[9] or '1') local arcRotate =arcAngle+90 if arcGap<0 then arcRotate=arcAngle-90 end local latF=arcY - fontSize + (0-(math.sin(math.rad(arcAngle))) * arcRadius) local lonF=arcX - fontSize +(0-(math.cos(math.rad(arcAngle))) * arcRadius) local step=1 for codepoint in mw.ustring.gcodepoint( arcText ) do -- block step=1,#arcText do table.insert(result,'<div style="position: absolute;') local posY=tostring(round( (latF + (math.sin(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius)) *ellipseFactor,2))..'px;' local posX=tostring(round( (lonF + (math.cos(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius))/ellipseFactor,2))..'px;' table.insert(result,' top: '..posY..' left: '..posX..' transform: rotate( '..tostring(round(arcRotate +((step-1)*arcGap)),2)..'deg);') table.insert(result,'width:'..tostring(fontSize*2)..'px; text-align: center; background-color:transparent; color: '..textColor..';') table.insert(result,'vertical-align: baseline; font-size: '..tostring(fontSize)..'px;">'..mw.ustring.char(codepoint)..'</div>') step=step+1 end end local function makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) local item={} itemdescription=stripdivs(itemdescription or '') local templon=lon if lon > 180 then templon=lon-360 end --for hemisphere+ or -1 dots if lon < -180 then templon=lon+360 end -- use 'real' coordinates for geohack label, while retaining shifted coords for plot if itemcolor=='transparent' then itemcolor='white' end itemcolor=getColor(itemcolor) -- ensure no opacity, which breaks maplink if string.find(itemcolor,'#')==1 and #itemcolor>7 then itemcolor=string.sub(itemcolor,1,7) end table.insert(item, '{ "type": "Feature", "properties": {') table.insert(item, ' "title": "'..itemtitle..'",') table.insert(item, ' "description": "'..itemdescription) table.insert(item, ' ([https://geohack.toolforge.org/geohack.php?params='..tostring(lat)..';'..tostring(templon)) table.insert(item, '_dim:2000 '..tostring(lat)..','..tostring(templon)..'])",') table.insert(item, ' "marker-symbol": "-number-'..string.gsub(group,'%W','')..'", "marker-size": "medium", "marker-color": "'..itemcolor..'" },') table.insert(item, ' "geometry": {"type": "Point", "coordinates": ['..tostring(lon)..','..tostring(lat)..'] } }') return table.concat(item) end local function makeLegendBox(result,args) local legend ={} local line,count, maxWidth='',1,8 local item line,item=extractItem(args.legendBox or '') local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end legend.Text = a -- convert hats to line breaks line=splitItem(line,6) -- (text, size, poition,background color, text/outline color, param options) legend.Size=line[2] or '150px80px1px' legend.Pos=line[3] or '10px10px' legend.Background=line[4] or 'beigeground' legend.Color=line[5] or 'darkbrown' legend.Param= line[6] or '' local argnum,legendCount,titleHeight='1',1,0 if (legend.Text and legend.Text~='') then titleHeight=15+(13.4*(select(2, string.gsub(legend.Text,"<br>", "")))) end local legendLine,legendGroup,legendY={},{},{} while args['legendItem'..argnum] do -- assign legendLine, legendGroup, legendY for each dot line,legendLine[legendCount] = extractItem(args['legendItem'..argnum] or '') line=splitItem(line,3) legendGroup[legendCount]=line[2] or argnum if shapeList[legendGroup[legendCount]] then maxWidth=math.max(tonumber(string.match(shapeList[legendGroup[legendCount]].shapeSize or '10','[%d]+')),maxWidth) else maxWidth=math.max(tonumber(string.match(shapeList['1'].shapeSize or '10','[%d]+')),maxWidth) end maxWidth=maxWidth+1 if line[3] then legendY[legendCount]=tonumber(string.match(line[3],'[%d]+')) else legendY[legendCount] = 3+maxWidth*(legendCount-1)+titleHeight -- if (legend.Text and legend.Text~='') then legendY[legendCount]=legendY[legendCount]+15 end end legendCount=legendCount+1 argnum=tostring(legendCount) end local w,h,r=getsize(legend.Size or '') local x,y=getsize(legend.Pos or '') local div=mw.html.create ('div') div:css('position', 'absolute') div:css('outline', '1px solid'..getColor(legend.Color)) if r~=0 then div:css('border-radius',tostring(r).."px") end div :css('top', y.."px") :css('left', x.."px") :css('width', w.."px") :css('height', h.."px") :css('line-height','105%') :css('background-color', getColor(legend.Background) ) :css('color','inherit') if not string.find(legend.Param,'noshadow') then div:css('box-shadow', '2px 2px 4px #33203335') end div:tag( 'div' ) :css('position', 'absolute') :css('top','1px') :css('left', (w/2).."px") :css('width',(w-8)..'px') :css('text-align', 'center') :css('color', getColor(legend.Color)) :css('transform', 'translateX(-50%)') :css('font-size','11px') :wikitext(legend.Text) :done() for lct=1,legendCount-1 do --local t=legendGroup[lct] local shape=shapeList[legendGroup[lct]] or shapeList['1'] local row={} row.gridx=3+maxWidth/2 row.gridy=(legendY[lct] or 0) + 5 if shape.shape=='image:' then row.gridy=row.gridy+6 end row.dx=0 row.dy=0 local legendShape= getshapetable(row,shape) div:wikitext(legendShape) :tag( 'div' ) :css('position', 'absolute') :css('top',(legendY[lct] or 0)..'px') :css('left', (maxWidth+6)..'px') :css('width', (w-maxWidth-6)..'px') :css('text-align', 'left') :css('line-height','103%') :css('color', getColor(legend.Color)) :css('font-size','10px') :wikitext(legendLine[lct]) :done() end div:allDone() table.insert(result,tostring(div)) end local function getQset (qValue,pValue,argNumber,args) local result={} local sortType=string.lower(args['geo-list-sort'] or 'a') local testValue='0' if sortType == 'alpha' or sortType == 'a' or sortType == 'ah' or sortType == 'al' then testValue = mw.wikibase.getLabel(qValue) elseif sortType == 'value' or sortType == 'v' or sortType == 'vh' or sortType == 'vl'then if qValue==nil or pValue == nil then debugmsg(mw.addWarning((qValue or 'nil')..' Qnumber for '..(pValue or 'nil'))) else if mw.wikibase.getBestStatements(qValue,pValue)[1] then testValue = mw.wikibase.getBestStatements(qValue,pValue)[1].mainsnak.datavalue.value.amount or '0' if pValue == 'P2046' then testValue = convertToSqKm(testValue, mw.wikibase.getBestStatements(qValue,pValue)[1].mainsnak.datavalue.value.unit) end end end elseif sortType == 'north' or sortType == 'n' or sortType == 'nh' or sortType == 'nl' then if mw.wikibase.getBestStatements(qValue,'P625')[1] then testValue = mw.wikibase.getBestStatements(qValue,'P625')[1].mainsnak.datavalue.value.latitude or 0 end elseif sortType == 'west' or sortType == 'w' or sortType == 'wh' or sortType == 'wl' then if mw.wikibase.getBestStatements(qValue,'P625')[1] then testValue = mw.wikibase.getBestStatements(qValue,'P625')[1].mainsnak.datavalue.value.longitude or 0 end end result[1]=testValue result[2]=argNumber return result end function p._main ( args ) local result={} local frame=mw.getCurrentFrame() local dotTable={} local magVal,scaleVal = args.magnify,'' local origH,origW=maplist.height,maplist.width if magVal then --set up the values needed to magnify the top-right portion of the map magVal=tonumber(string.match(magVal or '0',"[%.%-?%d]+")) or 1 if magVal>1 and magVal <=2 then maplist.height= round(maplist.height/magVal,0) maplist.width=round(maplist.width/magVal,0) scaleVal='transform: scale('..magVal..') translateY('..tostring((origH-maplist.height)/2)..'px);' else magVal=1 end end -- set up the three nested div boxes (plus an extra if centered) to put the map plus title/caption area, in an appropriate frame on the page if args.float=='center' or args.float=='centre' then table.insert(result,'<div class="center"><div class="thumb tnone">') elseif args.float=='left' then table.insert(result,'<div class="thumb floatleft">') else table.insert(result,'<div class="thumb floatright">') end table.insert(result,'<div class="thumbinner" style="position: relative; top: 0px; right: 0px; width: '..(args.width or "400")..'px;">') if args.title then table.insert(result,'<div class="center" style="font-weight:bold">'..args.title..'</div>') end if magVal and magVal >1 and magVal<=2 then table.insert(result,'<div class="thumbinner noresize" style="display:block; position: relative; outline:0px; border:0px; padding:0px; background-color:transparent; color:inherit;') table.insert(result,'top: 50%; right: '..tostring(0-((origW-maplist.width))/2)..'px; ') table.insert(result,'height: '..origH..'px; width: '..maplist.width..'px; '..scaleVal..'">') else magVal=1 table.insert(result,'<div class="thumbinner noresize" style="position: relative; outline:0px; border:0px; padding:0px;') table.insert(result,'top: 0px; right: 0px; ') table.insert(result,'height: '..maplist.height..'px; width: '..maplist.width..'px ">') end --add any boundary Qvalues to map-data-light if requested if args['geo-region'] and (args['geo-boundaries'] == '1') then local qmaps=args['map-data-light'] or '' if qmaps ~='' and string.sub(qmaps,#qmaps) ~= ',' then qmaps=qmaps..',' end local geoRegion = args['geo-region'] -- or add base-page here local geoSubRegion = args['geo-sub-region'] or 'P150' if not mw.wikibase.isValidEntityId(geoSubRegion) then geoSubRegion = 'P150' end if mw.wikibase.isValidEntityId(geoRegion) then local ct=1 local qtemp while (mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct]) do -- debugmsg('table '..ct..' - '..mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id) qtemp = mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id or nil if qtemp then if mw.wikibase.entityExists(qtemp) then qmaps=qmaps..qtemp..',' -- see if there is a geocode boundary Qvalue to add else debugmsg(mw.addWarning('unable to add geocode value for '..mw.wikibase.getLabel..','..qtemp)) end end ct=ct+1 end end if qmaps and qmaps ~= '' then -- add back the created/expanded list of boundary Qvalues, and remove trailing comma args['map-data-light'] = string.sub(qmaps,1,#qmaps-1) end end -- Create the basemap using mapframe local mapframecontent=getmapframecontent(args,'basemap') table.insert(result, frame:extensionTag{ name ='mapframe', content=mapframecontent, args={width=tostring(maplist.width), height=tostring(maplist.height), zoom=tostring(maplist.zoom), longitude=tostring(maplist.lonbase), latitude=tostring(maplist.latbase), mapstyle=maplist.mapstyle, frameless=true } } ) --Add coverall box to block the unhelpful links from mapframe - which wouldn't include all the dots. Reinstate some links to osm and wikimedia table.insert(result,'<div style="position: absolute;width:'..tostring(maplist.width)..'px; height:'..tostring(maplist.height)..'px;') table.insert(result,'top:0px;left:0px;background-color:#FFFFFF00; color:inherit;"></div>') --Add replacent hover-links for OpenStreetMap and maps terms and conditions table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-13)..'px; width: 12px; height: 12px">') table.insert(result,'[[file:Transparent.svg|12px|link=https://www.openstreetmap.org/copyright|'..aboutOSM..']]</div>') table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-110)..'px;') table.insert(result,'width: 12px; height: 12px background-color: transparent; color:inherit;">') table.insert(result,'[[file:Transparent.svg|12px| link=https://foundation.wikimedia.org/wiki/Policy:Maps_Terms_of_Use|'..termsOfUse..']]</div>') -- Add scale-line if not args.scalemark or args.scalemark~='0' then local top=maplist.height-42 local left=maplist.width-61-(tonumber(args.scalemark or '1')) local minipos=string.lower(args.minimap or '') -- scalemark gets pushed left if it would be behind the minimap if minipos~='' and not(string.find(minipos,'left') or string.find(minipos,'top') ) then local offset=tonumber(args.scalemark or '1')-tonumber(args['mini-width'] or '60') if offset<1 then left=maplist.width-61-tonumber(args['mini-width'] or '60') end end if maplist.width-left >216 then top=top+14 end -- shunt scaleline down if it is beyond the copyright stuff local scalek,scalem=getScale(maplist.zoom, maplist.latbase, magVal) local magReduce='' table.insert(result,"<div style=\"display:inline-block; position: absolute; background-color: #111111") table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;M0,8 l0,4 l20,0 l0,-4 l-0.3,0 l0,3.7 l-19.4,0 l0,-3.7 z&#39;) ") table.insert(result,"; width:20px") -- path is a 20px20px box, and then rescales if magVal==1 then table.insert(result,"; top:"..tostring(top-1).."px") table.insert(result,"; left:"..tostring(left+16).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5)..", "..tostring(1.5)..")") else -- shrink the scalemark to compensate for magnification table.insert(result,"; top:"..tostring(top-(magVal*0.28)).."px") table.insert(result,"; left:"..tostring(left+(16*(magVal*1.15))).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5*(1/magVal))..", "..tostring(1.5*(1/magVal))..")") magReduce= 'scale('..tostring(1/magVal)..')' end table.insert(result,"\"></div>") table.insert(result,'<div style="position: absolute; top: '..tostring(top)..'px; left: '..tostring(left+47)..'px; font-size: 9.5px; line-height: 126%; width: fit-content;') table.insert(result,'color: #444433; background-color: transparent; text-align: right; transform: '..magReduce..' translateX(-100%);">'..scalek..'<br>'..scalem..'</div>') end --Set up the shapeList and dotList tables, to provide data to go on the map local sgNumbers,sgSortable={},{} --s1,s2 sgNumbers["1"]="1" if args.useFormatStyle and args.useFormatStyle=='shortstyle' then shapeList=ParseShapeTypes (shapeList,args,"1") for argindex,argv in pairs(args) do -- build a list of all the numbered sg's that have been used if string.find(argindex,"sg[a-f,n%d]+") == 1 then -- only look through the sga,sgb, sgc,sgd,sge,sgf and sgn args local x=string.match(argindex,"[%d]+") -- find its number if x and not sgNumbers[x] then sgNumbers[x]=x end -- only add if not already found end if string.find(argindex,"sg[a-f]H") == 1 and highlightNum then highlightOption=true end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form for k,v in pairs(sgSortable) do -- work through the sorted list, parsing each set of sg's in turn, from 1 upwards shapeList=ParseShapeTypes (shapeList,args,v) end if highlightOption==true then shapeList=ParseShapeTypes (shapeList,args,'H') end end local dotList,dotresult,dotItemTable,dotGroupList={},{},{},{} if (not args.useFormatStyle) or args.useFormatStyle=='standardstyle' then local dotmax=0 local qset={} for indx,val in pairs(args) do if string.match(indx,'mark%-coord[%d]+') or string.match(indx,'lat[%d]+') or string.match(indx,'geo%-data[%d]+') then dotmax=math.max(dotmax, tonumber(string.match(indx,"[%d]+"))) if string.match(indx,'geo%-data[%d]+') then local argnum=string.sub(indx,9,#indx) or '' if args['mark-title'..argnum] == 'none' or (string.find(args['mark-title'..argnum] or 'x','nolist')== 1) then -- debugmsg('sort values ignored '..argnum..', '..args['mark-title'..argnum]) else qset[val]=getQset (val, (args['geo-data-type'] or 'P1082'), argnum, args) end end end if (indx=='shapeH') or (indx=='shape-colorH') or (indx=='shape-outlineH') or (indx=='label-colorH') then highlightOption=true end end local qlist=args['geo-data-list'] if qlist then if string.sub(qlist,#qlist) ~= ',' then qlist=qlist..',' end else qlist='' end local geoRegion = args['geo-region'] -- or add base-page here local geoSubRegion = args['geo-sub-region'] or 'P150' if not mw.wikibase.isValidEntityId(geoSubRegion) then geoSubRegion = 'P150' end if (args['find-geo-dots'] or '1') ~= '0' and geoRegion and mw.wikibase.isValidEntityId(geoRegion) then local ct=1 local qtemp while (mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct]) do -- debugmsg('table '..ct..' - '..mw.wikibase.getBestStatements(adminRegion,adminSubRegion)[ct].mainsnak.datavalue.value.id) qtemp = mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id or nil if qtemp then qlist=qlist..qtemp..',' -- add subregion Qvalue to the list end ct=ct+1 end end if qlist and #qlist>1 then -- create a table from the csv list local _, c = qlist:gsub(",","") local qtab=splitItem(qlist,c) for xd,yd in pairs(qtab) do yd=string.upper(yd) if qset[yd] == nil and mw.wikibase.isValidEntityId(yd) then dotmax=dotmax+1 qset[yd]=getQset (yd, (args['geo-data-type'] or 'P1082'), tostring(dotmax), args) args['geo-data'..tostring(dotmax)] = yd -- debugmsg(dotmax..' = '..(string.upper(yd) or 'Q Not found')..': '..(mw.wikibase.getLabel(yd) or 'label Not found')..': '..(mw.wikibase.getSitelink(yd) or 'link Not found')) elseif not mw.wikibase.isValidEntityId(yd) then debugmsg(mw.addWarning(yd..' is not a [[wikidata]] page. geo-data-list needs comma-separated Qvalues' )) else debugmsg(mw.addWarning(yd..' is duplicate')) end end end -- qset contains set of Qvalues and can be from wikidata geo-region, list of geo-data-list Q values or individual geo-data1= etc local ts = {} -- table to hold the keys local useQset=false for ky, val in pairs(qset) do table.insert(ts, {key = ky, value = val[1], num=val[2]}) useQset=true end if useQset then local order = string.lower(args['geo-list-sort'] or 'a') -- sort the list from previously stored sortkey, either Lowest or Highest first if string.find(',al,vl,nl,wl,west,w,alpha,a,' , ','..order..',') then table.sort(ts, function (a,b) return lessthan(a.value, b.value) end) else table.sort(ts, function (a,b) return morethan(a.value, b.value) end) end for ky,val in ipairs(ts) do -- assign values from sorted list to numbered parameter args['numbered'..val.num] = ky -- The numbered parameter becomes the value for each dot that has a geo Qvalue, from manual, list or wiki sources. end end dotItemTable=tradstyleParseShapes(args,dotItemTable,dotmax) for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) or args['geo-data'..x] then sgNumbers[x]=x -- add it to the list end end else for indx in pairs(args) do if string.match(indx,'dot[%d]+') then table.insert(dotList,indx) end --add the index name for dot1, dot2 etc to dotList end table.sort(dotList,morethan) for indx,dotName in pairs(dotList) do dotresult=ParseData(args,string.match(dotName,'[%d]+') ) -- using each dot number, assign the settings for each dot to a dotresult item line table.insert(dotItemTable,dotresult) -- and store that item line within the dotItemTable end end for arcVal = 65,91 do -- check through args looking for any arcs local arcLetter=string.char(arcVal) if args['arc-text'..arcLetter] or args['arc'..arcLetter] then makeArcText(args,result,arcLetter) end end local dotdivs='' local ddots=0 if dotItemTable[1] then local ddots=(dotItemTable[1].lat or 0)+(dotItemTable[1].lon or 0) end local fgroup='F'..tostring(maplist.latbase+maplist.lonbase+ddots ) local FullscreenList={} local addcomma='' for i,dotitem in pairs(dotItemTable) do -- working throug each dot item, merge the dot and shape values into a full set of css text local dotgroup= dotitem.group or "0" if dotitem.posType=='mark-line' and dotitem.markDest then --find destination xy values for any mark-lines for n,v in pairs(dotItemTable) do if v.code == dotitem.markDest then dotitem.gridx2=v.gridx dotitem.gridy2=v.gridy break end end end local qtype=dotitem.group -- find which shape group each dot has been assigned --debugmsg('dotgroup='..qtype..', sg='..(sgNumbers[qtype] or 'nil')..' , shapeList='..shapeList[qtype].shape) if not sgNumbers[qtype] then qtype="0" end --shapeList[dotitem.group] will give access to the shape values for that dot if highlightNum==dotitem.code and highlightOption==true and shapeList['H'] then table.insert(result, getshapetable(dotitem,shapeList['H'])) else table.insert(result, getshapetable(dotitem,shapeList[qtype])) -- Add the actual css instructions for each dot end if shapeList[dotgroup] and not string.find((dotitem.param1 or ''),'nomap') then -- only add if not excluded with 'nomap' labelText local ftext='' if dotitem.dotTag~='' and not string.match(dotitem.labelText or '','[%d]') then ftext=stripdivs(dotitem.dotTag or '')..' <br>' end if (dotitem.labelText ~= ftext) and dotitem.dotLink =='' then ftext=ftext..' '..stripdivs(dotitem.labelText)..'<br>' end if (dotitem.dotLink) and (dotitem.dotLink ~='') then ftext=ftext..dotitem.dotLink..'<br>' end if dotitem.imageName then ftext=ftext..'[[File:'..dotitem.imageName..'|250px]]' end table.insert(FullscreenList,1, makeFullscreenItem (string.gsub(ftext,"[\n]+"," "), dotitem.info,round(dotitem.lat,5),round(dotitem.lon,5),fgroup,shapeList[dotgroup].shapeColor)..addcomma ) addcomma=', ' end -- makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) -- Always add to start of list, to reverse the sequence, and separate with commas except for first item, which is now at the end end if args.legendBox then makeLegendBox(result,args) end if args.minimap or args['mini-locator'] then makeLocatorMap(args,result) end -- add tag link and details for fullscreen version addcomma='' if (mapframecontent or '[]') ~= '[]' then addcomma=',' end mapframecontent=getmapframecontent(args,'fullscreen') local contentstart='[ '..mapframecontent..addcomma..'{ "type": "FeatureCollection", "features": [ ' --extra features after first square bracket local contentend=' ] } ]' table.insert(result, '<div style="position: absolute;top: 9px;left: '..tostring(maplist.width-34)..'px">') table.insert(result, '<div style="color: white; opacity:100; font-size: 19px; font-weight:normal; text-align: left;">') table.insert(result, frame:extensionTag{ name ='maplink', content=contentstart..table.concat(FullscreenList)..contentend, args={zoom=tostring(maplist.zoom+1), class='no-icon', frameless='1', latitude=tostring(maplist.latbase), longitude=tostring(maplist.lonbase), --add invisble 'en-spaces' for tooltip text='<div title="'..fullscreenlinktext..'">&nbsp;&nbsp;&nbsp;</div>'} } ) table.insert(result,'</div></div>') -- end of maplink ----- -- add closing div for main map table.insert(result,'</div>') -- collate caption material to go in the outer div class if captionTable ~= '' and (not args['auto-caption']) then args['auto-caption']='on' end --default to include list if using geo values local autocaption=string.lower(args['auto-caption'] or 'no') local autoOff=autocaption:match("(%w+)(.*)") -- select the first word in autocaption and see if it is a negativeAnswer) if negativeAnswer[autoOff] then captionTable='' end if args.caption or (not negativeAnswer[autoOff]) then table.insert(result,'<div class="thumbcaption" style="text-align:left">') if args.caption then table.insert(result,args.caption) if captionTable ~= '' then table.insert(result,'<hr>') end end end local columns=tonumber(autoOff:match("[%d]+") or '1') if columns>1 then columns=round(maplist.width/(columns*17), 0) end -- convert from em to px for historical reasons --for k in pairs(dotList) do capchk=capchk..(args["dotlink"..k] or '') end local capchk=nil local captionList = {} for key, value in pairs(dotItemTable) do -- only add an autocaption line if there is both a dotTag and a dotLink line available and it is not marked as nolist if value.dotTag and value.dotTag~='' and (not string.find(value.param1 or '','nolist')) and string.gsub(value.dotLink or '',"%s+","")~='' then table.insert(captionList, {key = key, value = value}) if captionTable == '' then capchk=true else -- add the next line of content to the html table capchk=false end end end if captionTable ~= '' then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) for k,v in pairs(captionList) do local nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag local ngrp=v.value.group if captionTableText[ngrp] then -- debugmsg('table text for'..v.value.dotTag..', '..captionTableText[v.value.group][1]..', '..captionTableText[v.value.group][2]..', '..captionTableText[v.value.group][3]..' ('..captionTableText[v.value.group][4]..')') local c1,c2 c1,c2=checkColors(shapeList[ngrp].shapeColor) local cDot='<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;' cDot=cDot..'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div>' captionTable = htmlTableLine(captionTable,cDot,captionTableText[ngrp][1], captionTableText[ngrp][2], captionTableText[ngrp][3], captionTableText[ngrp][4]) end end end if capchk and (not negativeAnswer[autoOff]) then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) -- local myDivision = string.gsub((args.toggletext or toggletext), "%s+", "") if string.find(autocaption,'collaps') then table.insert(result,'<div class = "wikitable mw-collapsible ') if string.find(autocaption,'collapsed') then table.insert(result,'mw-collapsed') end table.insert(result,'" style="width:100%; line-height: 17px; border-style:none; text-align: center"><b>'..(args.toggletext or toggletext)..'</b>') end if string.find(autocaption, 'columns=') then columns=string.match(autocaption,'[%d]+',string.find(autocaption, 'columns=') ) end table.insert(result,'<div class="mw-collapsible-content" style="column-count:'..columns..'; column-rule:solid 1px;text-align:left;padding-top:5px">') table.sort(dotList,lessthan) local nval,ngrp='','0' for k,v in pairs(captionList) do nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag ngrp=v.value.group or '0' if v.value.dotLink and v.value.dotLink~='' and nval and nval~='' then local c1,c2 if nval==args.highlight then c1,c2=checkColors(shapeList['H'].shapeColor) else c1,c2=checkColors(shapeList[ngrp].shapeColor) end table.insert(result,'<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;') table.insert(result,'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div> '..v.value.dotLink..'<br>') end end table.insert(result,'</div>') -- end for caption-content div if string.find(autocaption,'collaps') then table.insert(result,'</div>') end -- end for toggle frame end if captionTable ~= '' then table.insert(result,captionTable..'</table>') end if args.caption or (not negativeAnswer[autoOff]) then table.insert(result,'</div>') end -- end for whole caption frame table.insert(result,'</div></div>') -- outer two frames if args.float == 'center' or args.float=='centre' then table.insert(result,'</div>') end if args['show-new-format'] == 'hints' then -- provide a 'format hint panel' in the 'Preview Box' local w="<small>Below are some template hints for the 'sga' compressed version, " w=w..'{{tl|OSM Location dots}}. It can use these, instead of the more verbose {{tl|OSM Location map}} parameter format. ' w=w..'Data is divided between a "ShapeGroup" and the "Dots", so that a single shapeGroup can be used for multiple dots on the map. ' previewMsg(w..'(nb. the "group" value can be the number or an assigned name of a shapeGroup)</small>') w='<small>{{tl|OSM Location dots}}: | dot(n)=group,lat,lon,dotTag | ' w=w..'dotlink=link/tooltip | dotlabel=label,position,dx,dy,param1,info | dotpic=filename <br>' w=w.."(nb. param1 options include 'nolink' 'nolist' 'nomap' 'hemisphere-1' 'hemisphere+1', 'noline' - quotes not required, separate with spaces).<br>" w=w..'| sga = Shape,Sizepx,Color,Angledeg | sgb= OutlineWidth,Color,Style | sgc=TextSize,Color,Angle,bold italic <br>' w=w..'| sgd=TagSizepx,Color,Spacer,Angledeg | sge=LineWidth,Color,Style | sgf=TextSpacingpx,LineHeight%,Outlinepx,backgroundColor<br>' w=w..'| sgn=Name (optional, to assign a meaningful name) | sgp=Parent (can be the name or number of the parent shapeGroup. ' previewMsg(w..'Each shapegroup will inherit values from a parent, stretching back to "sga1" and its default values.)</small>') if #pmsg then local dbg={} for i,x in pairs(pmsg) do table.insert(dbg,x..'<br>') end dbg=mw.addWarning(table.concat(dbg)) table.insert(result, dbg) end end if args.coordtest then debugmsg(mw.text.nowiki(args.coordtest)) end for i,x in pairs(msg) do table.insert(result, x..'<br>') end return table.concat(result) end function p.main(frame) local args = getArgs(frame) local itemTab={} maplist.width=tonumber(args.width) or 400 maplist.height=tonumber(args.height) or 300 if args.coord then itemTab=splitItem(convertCoordsTrad (args.coord),2) maplist.latbase=itemTab[1] maplist.lonbase=itemTab[2] else maplist.lonbase=tonumber(args.lon) or 5 maplist.latbase=tonumber(args.lat) or 0 end maplist.zoom=tonumber(args.zoom) or 1 visibleLinks=args.showlinks highlightNum=args.highlight if args.nolabels=='1' then maplist.mapstyle='osm' else maplist.mapstyle='osm-intl' end if maplist.width >= 400 and not(args['show-q-values']) then args['show-q-values'] = '1' -- default to on if wider than 400px elseif maplist.width < 320 then args['show-q-values'] = '0' end -- turn off Qvalus if frame is too narrow, even if requested return p._main(args) end return p gqsmiqj591a951mg947o6gd8jfmzvze 204449 204448 2026-07-05T14:35:47Z HaxelKaszëba 17492 204449 Scribunto text/plain require('strict') local delink=require('Module:Delink').delink local getArgs = require('Module:Arguments').getArgs local p = {} local maplist={} local sgNames={} local highlightOption=false local highlightNum local visibleLinks local geoTotalPop = 0 local captionTable = '' local captionTableText = {} -- This module creates framed maps of anywhere in the world, at the required scale, and enables annotations, -- dots, shapes, lines and other ways to customise the area of the map being shown. It also provides a link -- to an interactive fullscreen version, which has locator dots instead of annotations and shapes. -- This is the 2025 successor module to a wiki-markup template version of 2024, which itself was a successor -- to the 'Graph'/VEGA driven template that was begun in 2016, until the Vega version was switched off in 2023. -- This module is called from template {{OSM Location map}}, which uses the same parameter formats as before. -- In addition it may at some point be possible to use a more concise parameter format using the template {{OSM Location dots}} -- In general the css output from the two formats will be identical, but the the concise version will allow bits of -- greater control over some of the settings. -- see the documentation on the two template pages for details of how to use the mapping features. -- If language customisation is needed, there are text items below that can be translated. Also see the color table -- below with details of how to add additional color names to allow localised alternatives. -- (Translating other language shape-types could be possible, but has not currently been contemplated. -- Parameter name translation would be harder but likely to be possible, ideally still retaining compatibility -- with template calls already written using English). -- -------------- the following text variables are used within the template, and can be translated as required --------------- -- -------------- you may wish to retain the English version as a comment, to keep track of the original phrase -------------- local negativeAnswer={no=1,'0'-1,off=1} local fullscreenlinktext = 'Click for interactive fullscreen map with links to nearby articles' local toggletext = 'List of numbered map items:' local termsOfUse = 'Maps: terms of use' local aboutOSM = 'About OpenStreetMaps' local yards = 'yds' local miles = 'miles' local metres = 'm' local km = 'km' local sqkm = 'sq.km' local year = 'year' local notFound = 'not found' local totalItems = 'Total' local sumOfDatapoints = 'sum of datapoints' local numberCol = 'No.' -- abbreviated for top of column of code numbers local wikidataLink = 'Wikidata page' local shapeList={} --This sets up the 'factoryDefault' shape group 0 shapeList["0"]={shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border of the shape outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items text settings for labels textSZ="11px", textCL="darkgrey", textNG="0deg", --sgf further text settings textSP="0px", textLH="120%", textOL="0px", textBG="transparent", --sgd items for dotTag text settings tagSize="10px", tagColor="white", tagSpacer="0px", tagAngle="0deg", --sge items for extension line to connect label to dot textEW="0px", textEC="darkgrey", textES="solid" } local colorList={} -- used by colorLookup to catch unsupported colors (eg 'LimeGreen'), to convert to generic version colorList['green']='hardgreen' -- it could also be added to to include alternative language equivelants, for a quick solution. colorList['red']='hardred' -- colorList ['source'] = target colorList['white']='white' -- converts any color that includes 'source' into its equivelent target colorList['blue']='hardblue' -- note, for translation you can add to this list, rather than replace it, colorList['brown']='brown' -- which would mean existing map definitions in english would also still work, alongside translated ones colorList['grey']='hardgrey' colorList['gray']='hardgrey' colorList['purple']='hardpurple' colorList['orange']='hardorange' colorList['leaf']='hardleaf' --for a more thorough translation, you can add all the variants of the colors as further CTB elements and hex values or redirects local CTB={} -- set up a table of color names (the CTB Color table index) and html hash colorhex values. CTB["paleblue"],CTB["softblue"],CTB["hardblue"],CTB["darkblue"]="#D6E1EC","#77A1CB","#4B77D6","#1c559e" CTB["palered"],CTB["softred"],CTB["hardred"],CTB["darkred"] = "#FCC6C0","#EC644B","#DB3123","#AA1205" CTB["palegreen"],CTB["softgreen"],CTB["hardgreen"],CTB["darkgreen"]= "#D2F0E5","#81AF81","#269F46","#0b7527" CTB["paleleaf"],CTB["softleaf"],CTB["hardleaf"],CTB["darkleaf"]= "#dff5c1","#b5e376","#8cc244","#679c21" CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"]= "#E8E8D6","#AAAA88","#777755","#444433" CTB["palegray"],CTB["softgray"],CTB["hardgray"],CTB["darkgray"]=CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"] CTB["palebrown"],CTB["softbrown"],CTB["hardbrown"],CTB["darkbrown"]="#FAF6ED","#CCB56C","#AD7F14","#754910" CTB["palepurple"],CTB["softpurple"],CTB["hardpurple"],CTB["darkpurple"]="#e0d1e6","#c784e0","#a029cf","#7a05a8" CTB["paleorange"],CTB["softorange"],CTB["hardorange"],CTB["darkorange"]="#ffedc2","#ffcf61","#EEB533","#e39f05" CTB["black"],CTB["white"],CTB["yellow"]="#000000","#FFFFFF","#FAF039" CTB["background"],CTB["paleground"],CTB["beigeground"]="#f9f6f2","#FEFEFA","#F5F5DC" CTB["beige"]=CTB["beigeground"] CTB["aqua"],CTB["teal"],CTB["fuchsia"] = "#00FFFF","#008080","#FF00FF" CTB["maroon"],CTB["olive"],CTB["navy"] = "#800000","#808000","#000080" CTB["lime"],CTB["limegreen"],CTB["aquamarine"] = "#00FF00","#32CD32","#7FFFD4" CTB["silver"],CTB["yellow"],CTB["orchid"] = "#800000","#FFFF00","#DA70D6" -- set up a table of predefined clip-paths local pathshape={} pathshape.squaredd = "M 19,1.25 l 0,18 -18,0 0,-18 18,0m-1,1 -16,0 0,16 16,0 0,-16m-1,1 0,14 -14,0 0,-14 14,0zm-1,1 -12,0 0,12 12,0 0,-12zm-1,1 0,10 -10,0 0,-10 10,0z" pathshape.squared = "M 18,2.5 l 0,15 -15,0 0,-15 15,0m-1,1 -13,0 0,13 13,0 0,-13zm-1,1 0,11 -11,0 0,-11 11,0z" pathshape.triangledd="M 0 20,20 20,10 0,0 20ZM1.5 19,10 1.7,18.5 19,1.5 19ZM3 18,17 18,10 3.8,3 18ZM4.5 17,10 5.4,15.4 17, 4.5,17ZM6 16,13.8 16,10 7.4z" pathshape.triangled ="M1,18 l 18,0 l -9,-18 l -9,18zm1.7,-1.1 l 7.3,-14.6 l 7.3,14.6 l -14.6, 0zm1.7,-1 l 11.0,0 l -5.5,-11 l -5.5,11z" pathshape.circledd = "M0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm0.8,0a9.2,9.2 0 1,1 18.4,0a9.2,9.2 0 1,1 -18.4,0m1,0a8.2,8.2 0 1,0 16.4,0a8.2,8.2 0 1,0 -16.4,0zm0.8,0a7.2,7.2 0 1,1 14.8,0a7.2,7.2 0 1,1 -14.8,0m1,0 a6.4,6.4 0 1,1 12.8,0a6.4,6.4 0 1,1 -12.8,0z" pathshape.circled = "M2.5,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0m0.8,0 a5,5 0 1,1 11.4,0a5,5 0 1,1 -11.4,0" pathshape.diamond = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10z" pathshape.diamondd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm1,0 l 6,8.5 l 6,-8.5 -6,-8.5 -6,8.5zm1,0 l 5,-7 5,7 -5,7 -5,-7z" pathshape.diamonddd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm0.75,0 l 6.25,9 l 6.25,-9 -6.25,-9 -6.25,9zm0.75,0 l 5.5,-8 5.5,8 -5.5,8 -5.5,-8zm0.75,0 l 4.75,7 l 4.75,-7 -4.75,-7 -4.75,7zm0.75,0 l 4,-6 4,6 -4,6 -4,-6z" pathshape.crossd = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0zM2.3,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.cross = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0z" pathshape.thincross = "M2,12 l6,0 l0,6 l4,0 l0,-6 l6,0 l0,-4 l-6,0 l0,-6 l-4,0 l0,6 l-6,0z" pathshape.fivepointstar = "M10 0 L12.245 6.91 19.511 6.91 13.633 11.18 15.878 18.09 10 13.82 4.122 18.09 6.367 11.18 0.489 6.91 7.755 6.91Z" pathshape.fivepointstard = "M10 1.5 L 11.90825 7.3735 18.08435 7.3735 13.08805 11.003 14.9963 16.8765 10 13.247 5.0037 16.8765 6.91195 11.003 1.91565 7.3735 8.09175 7.3735 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sixpointstar = "M10 0 L12.323 5.977 18.66 5 14.645 10 18.66 15 12.323 14.023 10 20 7.677 14.023 1.34 15 5.355 10 1.34 5 7.677 5.977Z" pathshape.sixpointstard = "M10 1.5 L 11.97455 6.58045 17.361 5.75 13.94825 10 17.361 14.25 11.97455 13.41955 10 18.5 8.02545 13.41955 2.639 14.25 6.05175 10 2.639 5.75 8.02545 6.58045 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sevenpointstar = "M10 0 L12.048 5.747 17.818 3.765 14.602 8.95 19.749 12.225 13.69 12.943 14.339 19.01 10 14.72 5.661 19.01 6.31 12.943 0.251 12.225 5.398 8.95 2.182 3.765 7.952 5.747Z" pathshape.sevenpointstard = "M10 1.5 L11.7408 6.38495 16.6453 4.70025 13.9117 9.1075 18.28665 11.89125 13.1365 12.50155 13.68815 17.6585 10 14.012 6.31185 17.6585 6.8635 12.50155 1.71335 11.89125 6.0883 9.1075 3.3547 4.70025 8.2592 6.38495 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.eightpointstar = "M10 0 L11.88 5.46 17.071 2.929 14.54 8.12 20 10 14.54 11.88 17.071 17.071 11.88 14.54 10 20 8.12 14.54 2.929 17.071 5.46 11.88 0 10 5.46 8.12 2.929 2.929 8.12 5.46Z" pathshape.eightpointstard = "M10 0 L10 1.5 L11.598 6.141 16.01035 3.98965 13.859 8.402 18.5 10 13.859 11.598 16.01035 16.01035 11.598 13.859 10 18.5 8.402 13.859 3.98965 16.01035 6.141 11.598 1.5 10 6.141 8.402 3.98965 3.98965 8.402 6.141ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.ring="M2.6,9.5a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.semicircle = "M0,5 C0,18.3,20,18.3,20,5 L 0,5z" pathshape.arrowhead = "M 10,10.2 l 10,2.8 -2,-3 2,-3 -10,2.8 -0.2,0.2z" pathshape.boxd=pathshape.squared pathshape.boxdd=pathshape.squaredd pathshape.ellipsed=pathshape.circled pathshape.ellipsedd=pathshape.circledd local msg={} local function debugmsg(txt) table.insert(msg,txt) end local pmsg={} local function previewMsg(txt) table.insert(pmsg,txt) end local function colorLookup(color) for c,l in pairs(colorList) do if string.find(color,c) then return l end end return color end local function getColor (color,chk) local c local opacity="100" if not color or color=='' then color='hardgrey' end if color=="transparent" then return color end if color=="background1" then color='background' end if string.byte(color,1,1)==35 and (#color == 7 or #color == 9) then c=color elseif string.byte(color,1,1)==35 and #color == 4 then c=string.sub(color,1,2)..'f'..string.sub(color,3,3)..'f'..string.sub(color,4,4)..'f' else local s=color..'1' s= s:sub(0,s:find("%d")-1) opacity=string.match(color,"%d+") if not CTB[s] then s= colorList[s] end -- check for synonyms and translations if not CTB[s] then debugmsg(mw.addWarning('color = '..color..'. The color name is not defined. Used default grey instead')) end c=CTB[s] or CTB.hardgrey end if opacity and (tonumber(opacity) < 100) and string.find(c,"#")==1 and string.len(c)==7 and opacity~="" then local hexval=string.format("%x",(math.floor(tonumber(opacity)*2.55))) c=c..hexval end return c end function p.colorvalue(frame) -- enable external access to the CTB colorTable values. usage: {{#invoke:OSM Location map|colorvalue|color=hard blue}} local c if not frame.args.color or frame.args.color=='' then c='grey' else c=string.lower(string.gsub(frame.args.color,'%s+','')) end return string.upper(string.sub(getColor(c),2)) end local function checkColors(color) local c=getColor(color,'check') local opacity =1 -- calculate colour brightness and return black or white for contrast if c=='transparent' then return c,'#000000',0 end if not (string.find(c,'#')==1) then return c,'#FFFFFF',0 end if #c>8 then opacity= tonumber('0x'..(string.sub(c,8,9)))/500 end local r=tonumber('0x'..(string.sub(c,2,3)))/255 local g=tonumber('0x'..(string.sub(c,4,5)))/255 local b=tonumber('0x'..(string.sub(c,6,7)))/255 if 0.2126 * r + 0.7152 * g + 0.0722 * b / opacity < 0.7 then return c,'#FFFFFF',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity else return c,'#000000',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity end end local function morethan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) and tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%-?%f[%d]%d[,.%d]*%f[%D]') ) b = tonumber(string.match(b, '%-?%f[%d]%d[,.%d]*%f[%D]') or '0') end return a>b end local function lessthan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) and tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%-?%f[%d]%d[,.%d]*%f[%D]')) b = tonumber(string.match(b, '%-?%f[%d]%d[,.%d]*%f[%D]') or '0') end return a<b end local function getsize(size) --size1 is between 1 and 3 values, each with px, equating to width,height,corner-rounding --eg '15px 25px 5px' (spaces are optional) or '18px'. returns three numbers local sizeval = {} for v in string.gmatch(size, "[^px]+") do table.insert(sizeval,v) end sizeval[1] = tonumber(sizeval[1]) or 13 sizeval[2] = tonumber(sizeval[2]) or sizeval[1] sizeval[3] = tonumber(sizeval[3]) or 0 return sizeval[1],sizeval[2],sizeval[3] end local function coord2text(coord) -- looks through the output from {{coord}} to find the lat and long decimal values -- and converts compass points to minus or not-minus, return with separating comma. local lat = string.match(coord,'[%.%d]+°[NS]') local lon = string.match(coord,'[%.%d]+°[EW]') local neg={N="",S="-",W="-",E=""} return neg[string.match(lat, '[NS]')]..string.match(lat,'[%.%d]+')..","..neg[string.match(lon, '[EW]')]..string.match(lon,'[%.%d]+') end local function convertCoordsTrad (row) local coords='' if row and string.find (row,'<span class="geo">') then local a,b=string.find(row,'<span class="geo">') local start=b+1 a,b=string.find(row,"</span>",b) local finish=a-1 coords=string.sub(row,start,finish) coords=string.gsub(coords,'; ',',') end return coords end local function convertCoords (row) local start,finish,lat,lon,coords,says if row then local a,b=string.find(row,"<span class=") start=a while a do -- find the final span> finish=b a,b=string.find(row,"span>",b) end if start then coords= string.sub(row,start,finish) says="" if string.find(coords,'<span class="error">') then error("coord error: badly formed coordinates",0) end coords=coord2text(coords) coords = string.sub(row,1,start-1)..coords..string.sub(row,finish+1) else coords=row end return coords else return "Nothing to see here" end end local function fillCommas(val,max) local line='' if not val then line=',' -- ensure there is some content else line = val --string.lower(string.gsub(val,"%s+","")) -- or strip spaces end if string.find(line,',') == 1 then line=' '..line end -- ensure initial comma is not skipped local _, count=string.gsub(line,",","") -- add enough subsequent commas for all entries line=line..string.rep(',',max-count) while(string.find(line,",,") ) do line=string.gsub(line,",,",", ,") --ensure string.gmatch doesn't ignore any empty items by padding with spaces end return line end local function makeLinkBox(left,top,wid,label, link) local linkBoxName='Transparent square.svg' if visibleLinks or '' =='yes' then linkBoxName='Red hollow square.svg' end local builder = mw.html.create('div') --display:inline-block; local t= string.find(label,'wikidata') -- exclude wikidata from tooltip line if t then label=string.sub(label,1,t-2) -- don't show wikidata value in tooltip if string.find(link,'wikidata') then link = '' end -- don't link on click to wikidata (if there was not another link) end builder :cssText('position:absolute;left:'..tostring(left-1-wid/2)..'px;top:'..tostring(top-1 + math.min(wid/2-12,0) - wid/2)..'px') :wikitext(string.format( '[[File:%s|%dpx|link=%s|%s]]', linkBoxName, wid+2, link, label )) return tostring(builder) end local function extractItem(row,searchItem) -- remove text following a searchItem or start of line, which might be in quote-marks to allow commas local xend,xstart=1,0 if not row then return '','' end if searchItem then xend,xstart= string.find(row or '',searchItem or 'image:') end if not xstart then return string.gsub(string.gsub(row or '',"%b\"\"", ''),"%b\'\'", '') or '','' end while row:byte(xstart+1) == 32 and xstart<#row do -- skip over any leading spaces xstart=xstart+1 end local xbyte=row:byte(xstart+1) if xbyte == 34 or xbyte == 39 then -- are they wrapped in single or double quotes xstart=xstart+1 xend=row:find(string.char(xbyte),xstart+1) else xend = row:find(',',xstart+1) -- if no quotes, we assume no commas if not xend then xend=#row+1 end end -- return residual row and extracted text return row:sub(0,xstart)..row:sub(xend), row:sub(xstart+1,xend-1) end local function itemCheck(item,ext) if not item then return nil end if not ext then ext='' end return (string.match(item,"[%.%-?%d]+") or '0')..ext end local function stripdivs(line) return string.gsub(line or '',"%b<>", ' ') end local function splitItem(item,max) -- takes a commas-sep list and returns a table of lowercase items with no spaces, or nil local r={} local x=1 item=string.lower(fillCommas(item,max)) for t in string.gmatch(item,"[^,]+") do r[x]=string.gsub(t,"%s+","") if r[x]=='' then r[x]= nil else -- residual items might have commas if x>max then r[max]=(r[max] or '')..', '..r[x] end end x=x+1 end return r end local function ParseShapeTypes (result,args,sgval) -- for use with compressed, comma-separated 'sg plus dots' parameters --[[ shape table items and default values as set at top of page shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items label text textSZ="11px", textCL="white", textNG="0deg", textAT=attributes ("bold" and/or "italic") --sgd items for dotTag tagSize="11px", tagColor="darkgrey", tagSpacer="0px", tagAngle="0deg", --sge extension line textEW="1px", textEC="darkblue", textES="solid" --sgf fx for text labels textSP="0px" textLH="100%" textOL="1px", textBG="paleground", <!--| sga = shape,Size,Color,Angle|sgb= outlineWidth,color,style eg: sga1=circle,14px,blue,0deg| sgb1=0px,dark grey,solid | sgc=textSize,color,angle,bold/italic | sgd=tagSize,Color,Spacer,Angle eg: sgc1=11px,dark grey,0deg,normal| sgd1=9px,white,0px,0deg | sge=lineWidth,color,style |sgf=textspacing,lineHeight%,outlinepx,backgroundcolor, [bold,italic] eg: sge1=0px,black, solid| sgf1=0px,120%,0px,background | dot=shape-group/lat/lon/title/dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> --]] if args["sgn"..sgval] then local sgname=string.match(args["sgn"..sgval],"(%w+)(.*)") sgNames[sgname]=sgval end local parent= args["sgp"..sgval] if parent then parent=string.match(parent,"(%w+)(.*)") local pos= string.find(parent,"%d+") if pos == 1 then parent=string.match(parent,"%d+") else parent=sgNames[parent] or '1' end end if sgval~='H' then if not parent or tonumber(parent) > tonumber(sgval) then if sgval=="1" then parent="0" else parent="1" end end end local itemTab, line, filename result[sgval]={} result[sgval].shapeType=sgval line,filename=extractItem(args['sga'..sgval] or '','image:') if sgval=='1' and not args.sga1 then line='circle,12px,blue,0deg' end -- ensure there is a parent=1 sga result[sgval].shapeFile=filename or '' -- sga= Attributes for shape itemTab=splitItem(line,4) result[sgval].shape = itemTab[1] or result[parent].shape result[sgval].shapeSize=itemTab[2] or result[parent].shapeSize result[sgval].shapeColor=itemTab[3] or result[parent].shapeColor result[sgval].shapeAngle=itemCheck(itemTab[4],'deg') or result[parent].shapeAngle -- sgb= Border outline attributes for shape itemTab=splitItem(args['sgb'..sgval],3) result[sgval].outlineWidth=itemCheck(itemTab[1],'px') or result[parent].outlineWidth result[sgval].outlineColor=itemTab[2] or result[parent].outlineColor result[sgval].outlineStyle=itemTab[3] or result[parent].outlineStyle --sgc=character attributes for label itemTab=splitItem(args['sgc'..sgval],4) result[sgval].textSZ=itemCheck(itemTab[1],'px') or result[parent].textSZ -- size of text in px result[sgval].textCL=itemTab[2] or result[parent].textCL -- colour for text result[sgval].textNG=itemCheck(itemTab[3],'deg') or result[parent].textNG -- Angle for text result[sgval].textAT=itemTab[4] or result[parent].textAT -- attributes bold, and/or italic --sgd=dotTag attributes itemTab=splitItem(args['sgd'..sgval],4) result[sgval].tagSize=itemCheck(itemTab[1],'px') or result[parent].tagSize result[sgval].tagColor=itemTab[2] or result[parent].tagColor result[sgval].tagSpacer=itemCheck(itemTab[3],'px') or result[parent].tagSpacer result[sgval].tagAngle=itemCheck(itemTab[4],'deg') or result[parent].tagAngle --sge= extension line attributes itemTab=splitItem(args['sge'..sgval],4) result[sgval].textEW=itemCheck(itemTab[1],'px') or result[parent].textEW -- width result[sgval].textEC=itemTab[2] or result[parent].textEC -- colour result[sgval].textES=itemTab[3] or result[parent].textES -- style --sgf= fx for label text itemTab=splitItem(args['sgf'..sgval],4) result[sgval].textSP=itemCheck(itemTab[1],'px') or result[parent].textSP -- spacing value for letters result[sgval].textLH=itemCheck(itemTab[2],'%') or result[parent].textLH -- Angle for text result[sgval].textOL=itemCheck(itemTab[3],'px') or result[parent].textOL -- width of text-border line result[sgval].textBG=itemTab[4] or result[parent].textBG -- color for text background return result end local function round(x,dec) -- x=number [, dec=integer] returns numeric value with upto dec decimals (all but first trailing zeros get truncated) if (dec or 0)==0 then return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5) --this avoids .0 where dec=0 end dec =10^(dec) return x>=0 and math.floor(x*dec+0.5)/dec or math.ceil(x*dec-0.5)/dec end local function maptogrid(t,r,test) --[[ converts mercator projection longitude and latitude coordinates to x and y pixel coordinates, within a frame of given size, centre coordinates and zoom level. t is a table of named indices: {lon, lat, lonbase, latbase, width, height, zoom} output is two values, x and y, rounded to r decimal places--]] local x,y x=t.width/2 + ( ((math.rad(t.lon)*6378137) - (math.rad(t.lonbase)* 6378137) ) / (156543.03*math.cos(t.latbase/180)/(2^t.zoom) ) )*(1-(0.075*(math.abs(t.latbase)/90))) y=t.height/2 + ( ( (math.log(math.tan(math.rad(t.latbase)/2+math.pi/4))*6378137) - (math.log(math.tan(math.rad(t.lat)/2+math.pi/4))*6378137) ) / (156543.03*math.cos(t.latbase/180) / (2^t.zoom) ) )*(1-(0.075*(math.abs(t.latbase)/90))) return round(x,r),round(y,r) --source: python code at https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames and https://wiki.openstreetmap.org/wiki/Mercator --[[ width and height are the size, in pixels, of the map, which will be centerd around lonbase,latbase. Method: Convert lon and lonbase to meter-offsets from coord(0,0), and subtract lonbase from lon, zoom and latbase are used to scale the resulting meter-offset to pixels, and add it to width/2. Convert lat and lat-base to meter-offsets from coord(0,0), subtract lat from latbase, scale the resulting meter-offset to pixels, add it to height/2. A correction factor '*(1-(0.075*(math.abs(t.latbase)/90) ) )' compensates for an error that seems to creep in towards the edges of the map at higher latitudes. It was identified experimentally, and ensures a dot at the edge is in the same place as if that location is positioned at the centre. Original Python code for lat,lon to x,y where 0,0 is the centre of the map print('x=',width+(((math.radians(lon1) * 6378137)- (math.radians(lonbase) * 6378137))/ (156543.03*math.cos(latbase/180)/(2**zoom))),' y=',height+(( (math.log(math.tan(math.pi / 4 + math.radians(latbase) / 2)) * 6378137)- (math.log(math.tan(math.pi / 4 + math.radians(lat1) / 2)) * 6378137))/ (156543.03*math.cos(latbase/180)/(2**zoom))) ) --]] end local function getScale(zoom, lat,magVal) lat = tonumber(lat) if not lat then error("getScale: lat is nil") end if magVal and magVal > 1 and magVal <= 2 then zoom = zoom + (magVal - 1) end local dist = (156543.03 * math.cos(math.rad(math.abs(lat))) / (2 ^ zoom)) / 17 local y if dist < 1 then y=(round(dist*10,1)) return tostring(y*100)..metres, tostring(round(y*109,0))..yards elseif dist <18 then y=(round(dist,0)) return tostring(y)..km, tostring(round(y*0.621371,1))..miles elseif dist <500 then y=round(dist/10) return tostring(y*10)..km, tostring(round(y*6.21371,0))..miles else y=round(dist/100) return tostring(y*100)..km, tostring(round(y*62.1371,0))..miles end end local function ParseData (args,dotval) -- for use with compressed, comma-separated 'sg plus dots' parameters -- takes a structured series of comma-separated items which get parsed as the following: -- dot(n)= (sgNumber or Name),{{coord}} or (lat,lon), (dotTag) -- dotlink(n) = single-parameter text to give wikilink and/or title used by tootlip, fullscreen dots and autocaption list -- dotlabel(n) = 'label text',pos(left,roght,top,bottom,centre,auto),(dx), (dy) pixel offsets, params, info -- dotpic(n) = single parameter wikimedia filename for an image to use in photopanel and/or fullscreen dots -- dotfeature(n)= 'mark-line' (,linewidth,style,gap) or 'photo-panel' (,image-dim,panel-width,panel-height), draws line to n-1 -- label is used if either a position and/or an x,y offset are not 0,0 ( if no label then dotTag will be put at at the x,y offset, or over the dot -- label text can be autoaligned if x,y puts it left or right of the dot, or centered if above/below) -- quote marks are not needed unless including commas within the label text -- param1 is optional items separated by spaces, and can include [nolabel nolist nomap hemisphere+1 hemisphere-1] -- info is free wikitext, to be used in the fullscreen dot box. (use dotpic to show a picture) --<!--| dotx=shape-group,[lat,lon or {{coord}} ], dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> local result={} local count=1 local row = convertCoords (args["dot"..dotval]) -- swap in any {{coord}} values so they are csv lat and lon row=fillCommas(row,4) result.code=dotval -- store the parameter name as the id code for item in string.gmatch(row,"[^,]+") do -- iterate through 'row', adding each csv item in turn, if present if count==1 then --see if it is a number or a name local pos= string.find(item,"%d+") if pos == 1 then result.group=string.lower(string.gsub(item,"%s+","")) else item=string.match(item,"(%w+)(.*)") -- ensure just a single word result.group=sgNames[item] end elseif count==2 then result.lat=tonumber(string.match(item,"[%.%-?%d]+")) or 0-- find the number, with no non-numeric stuff elseif count==3 then result.lon=tonumber(string.match(item,"[%.%-?%d]+")) or 0 elseif count==4 then result.dotTag=item:match( "^%s*(.-)%s*$" ) or "" -- dotTag allows for internal spaces, but no commas end count=count+1 end row, result.labelText= extractItem(args["dotlabel"..dotval]) result.labelText= string.gsub(result.labelText,"[%^]+","<br>") -- convert hats to line breaks local item=splitItem(row,6) result.labelPos=item[2] or 'center' result.dx=tonumber(string.match(item[3] or '0',"[%.%-?%d]+")) or 0 result.dy=tonumber(string.match(item[4] or '0',"[%.%-?%d]+")) or 0 result.param1=string.lower(item[5] or '') if string.find(result.param1,"hemisphere-1",1,true) then result.lon=result.lon-360 elseif string.find(result.param1,"hemisphere+1",1,true) then result.lon=result.lon+360 end local txt = '' if item[6] then -- ensure all info elements are included, including commas count=1 local max=6 for t1 in string.gmatch(fillCommas(row,max),"[^,]+") do if count>max then txt=txt..',' end if count>=max then txt=txt..t1 end count=count+1 end end result.info=txt result.imageName = args['dotpic'..dotval] -- Get first wikilinked item (if any) from the args.dotlink and set this plus the delinked text local testx=args["dotlink"..dotval] or '' result.dotLink=testx if testx ~= '' then testx=stripdivs(testx) result.title=delink({ testx }) local linkstart= string.find(testx,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then result.dlink=delink( { string.sub(testx,linkstart,string.find(testx,']]',1,true)+1),wikilinks='target' } ) else result.dlink='' end else result.dlink='' result.title='' end if args['dotfeature'..dotval] then local item=splitItem(args['dotfeature'..dotval],6) if (item[1] or '') =='photo-panel' then result.ppwidth= tonumber(string.match((item[3] or '110'),"%d+")) -- panel width result.ppheight= tonumber(string.match((item[4] or '48'),"%d+") ) --panel height result.photowidth=round(tonumber(string.match((item[2] or '1.3'),"[%.%-?%d]+")) * result.ppheight+1,0) -- dimension to set image size result.photoImage=result.imageName result.posType='photo-panel' elseif (item[1] or '') == 'mark-line' then local x=tonumber(dotval or '0') result.markDest=item[5] or tostring(x-1) result.mlWidth= tonumber(string.match((item[2]) or '',"%d+") or '1') result.mlStyle= item[3] or 'solid' result.mlGap=tonumber(string.match((item[4] or ''),"[%d]+") or '0') result.posType='mark-line' end --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end maplist.lon=result.lon maplist.lat=result.lat result.gridx, result.gridy = maptogrid(maplist,1) -- convert geo coords to grid xy - using values from maplist table return result end local function multiCheck (args, argName, argVal, defVal, alt) if not alt then alt='nonexistant' end if argVal=='H' and not args[argName..'H'] then argVal=highlightNum or '1' end if argVal=='' then return (args[argName] or args[alt] or (args[argName..'D']) or args[alt..'D'] or defVal) -- unnumbered args do not inherit from D or 1 else return (args[argName..argVal]) or (args[alt..argVal]) or (args[argName..'D']) or (args[alt..'D']) or (args[argName..'1']) or (args[alt..'1']) or defVal end end local function htmlTableLine (htmlTable,h1,h2,h3,h4,h5,type) if type == 'header' then if (h4 or '') ~= 'no-year' then h4='<th class="unsortable">'..h4 or ''..'</th>' else h4 = '' end if (h5 or '') ~= '' then h5='<th class="unsortable">'..h5..'</th>' else h5 = '' end return htmlTable..'<tr><th>'..(h1 or '')..'</th><th>'..(h2 or '')..'</th><th>'..(h3 or '')..'</th>'..h4..h5..'</tr>' else if (h4 or '') ~= 'no-year' then h4='<td>'..h4 or ''..'</td>' else h4 = '' end if (h5 or '') ~= '' then h5='<td>'..h5..'</td>' else h5 = '' end return htmlTable..'<tr><td>'..(h1 or '')..'</td><td>'..(h2 or '')..'</td><td>'..(h3 or '')..'</td>'..h4..h5..'</tr>' end end local function formatInt(number) if number == 0 then return notFound end local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)') int = int:reverse():gsub("(%d%d%d)", "%1,") return minus .. int:reverse():gsub("^,", "") .. fraction end local function convertToSqKm (value,punit) if string.find(punit, 'Q35852') then value = value / 100 -- convert to sq km if it's hectares elseif string.find(punit, 'Q232291') then value = value /(1/2.59) -- or this if it's square miles elseif string.find(punit, 'Q81292') then value = value / 247.1 -- or this if it's acres end return value end local function assignTradstyleShape(shapeResult,default,dotResult,args,nval) local item,itemTab local autoDotTag='' local shapeWidth,shapeHeight=0,0 local argval=nval -- to catch the unnumbered shape series local qvalue='' if argval=='0' then argval='' end if nval=='H' then shapeResult.H={} end -- Assign autovalues if a wikidata Qvalue has been supplied -- some preliminary documentation -- geo-data-type= default is P1082 (population) -- other options might include number of households (P1538) and per capita income (P10622) or land area (P2046) -- geo-data-list= this is a comma-separated list of Q values, for a manually generated list that doesn't require nymbered parameters -- geo-scalefactor = can enlarge or shrink all the dots in proportion, depending an what the map needs -- geo-region = needs to be a Qvalue of the larger administrative region that the settlements/sub-regions are in -- geo-sub-region needs to be a Pvalue, but will default to P150 (some pages use 'has parts' = P527) -- geo-data3= etc numbered Qvalue item, if used in conjunction with other shape/label parameters -- shapeD= sets default shape, which migh be n-circle to include numbers. -- geo-number-size= will give a fixed size for shape numbers. (defaults to 12 when using proportional dots) -- geo-boundaries= if set to 1 will add any Qvalues for the sub-regions to 'map-data-light'. -- show-geo-dots= to use proportional dots. Will be fixed size if 0. (set mark-sizeD=0 to have no dot at all, eg to show labels or boundaries) -- find-geo-dots=1 or 0 (needs to also include geo-region) -- label-posD = gets additional options: [off] gives no label, [on] will position lower centre, -- [on+] includes numeric values beside/below label, [on^] will always put it below. [on-] always puts values beside if args['geo-data'..argval] then -- and (args['mark-title'..argval] or '') ~= 'none' then local pvalue = args['geo-data-type'] or 'P1082' local plabel = mw.wikibase.getLabel(pvalue) if not args['geo-number-size'] then args['geo-number-size']=12 end if not args['label-posD'] then args['label-posD'] = 'on' end qvalue=args['geo-data'..argval] -- total poulation is a Qvalue wikidata page entry, or add up all the geo-data values or use a random 10000 local region = args['geo-region'] -- or mw.wikibase.getEntityIdForCurrentPage() local thisPop=0 local areatxt='' local yearVal='' if mw.wikibase.getBestStatements(qvalue,pvalue)[1] then thisPop=tonumber(mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.amount) or 0 if pvalue == 'P2046' then -- area, so convert from ha to sq km if needed (/100) thisPop = convertToSqKm(thisPop, mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.unit) end if mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers and mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers.P585 then yearVal = mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers.P585[1].datavalue.value.time if yearVal then yearVal = yearVal:sub(2,5) end -- wikidata reports an entire date and time, so extract the year end else debugmsg(mw.addWarning(mw.wikibase.getLabel(qvalue)..' ('..qvalue..') has no recorded '..plabel)) end local scalefactor=args['geo-scalefactor'] or 1 if argval=='1' and args['mark-size1'] and not args['mark-sizeD'] then args['mark-sizeD'] = args['mark-size1'] end if argval=='1' and args['label-pos1'] and not args['label-posD'] then args['label-posD'] = args['label-pos1'] end if not args['mark-size'..argval] then if args['show-geo-dots'] == '0' then args['mark-size'..argval] = args['mark-sizeD'] or '1' -- just use a 'point' for the label to attach to else if thisPop>0 then args['mark-size'..argval] = round(math.sqrt(thisPop/geoTotalPop*100) * 8 * scalefactor,0) else args['mark-size'..argval] = args['mark-sizeD'] or '1' args['shape-outline'..argval]=args['shape-outline'..argval] or args['shape-outlineH'] end end end local posTemp=string.lower(multiCheck(args,'label-pos',argval,'on')) args['label-pos'..argval] = posTemp posTemp=splitItem(posTemp,2)[1] if pvalue == 'P2046' then thisPop = round(thisPop,2) areatxt=sqkm..areatxt -- round decimals if sq km area elseif pvalue == 'P10622' then if mw.wikibase.getBestStatements(qvalue,pvalue)[1] then local punit=(mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.unit or ''):match("Q.*") -- extract Q value if punit == 'Q4917' then areatxt=' US$' elseif punit == 'Q4916' then areatxt=' euro' elseif punit == 'Q25224' then areatxt=' £GBP' else areatxt=mw.wikibase.getLabel(punit)..areatxt end end end if posTemp=='off' then else if not args['label'..argval] then args['label'..argval] = mw.wikibase.getLabel(qvalue) end local tline=posTemp:find("[%+%^%-]") if tline then local tval=posTemp:sub(tline,tline) local eline posTemp=posTemp:sub(1,tline-1) if posTemp == 'left' or posTemp == 'right' or string.find(posTemp,'east') or string.find(posTemp,'west') then if tval == '^' then eline = '^' else eline = ' ' end -- only add line break if 'forced', used for left/right elseif tval == '+' or tval == '^' then eline = '^' -- add linebreak unless 'forced' not to else eline = ' ' -- must be a dash, so no line break end args['label'..argval] = args['label'..argval]..eline..'('..formatInt(thisPop)..')' args['label-pos'..argval] = posTemp..args['label-pos'..argval]:sub(tline+1,#args['label-pos'..argval]) end end if posTemp=='on' then if argval=='1' and (not args.ldyD) then args.ldyD = args['ldy'..argval] or '0' end if not args['ldy'..argval] then args['ldy'..argval] = args.ldyD end -- ensure ldy for that dot is added to the position args['label-pos'..argval]='center' local _,br = string.gsub(args['label'..argval],'%^','') local item=splitItem(args['label-size'..argval],2) args['ldy'..argval] = (args['ldy'..argval] or 0) + ((item[1] or 11) * ((br+1)*0.6)) + ((args['geo-number-size'] or 12) / 2) end local nt='' if args['mark-title'..argval] == 'nolist' then nt='nolist,' end if not args['mark-title'..argval] or args['mark-title'..argval] == '' or args['mark-title'..argval] == 'nolist' then if mw.wikibase.getSitelink(qvalue) then args['mark-title'..argval] = nt..'[['..mw.wikibase.getSitelink(qvalue)..']]' else args['mark-title'..argval] = nt..mw.wikibase.getLabel(qvalue) end end -- If mark-title is not set locally then attempt Sitelink value, or use label if thisPop == 0 then areatxt='' end captionTableText[argval]={} captionTableText[argval][1]= args['mark-title'..argval] captionTableText[argval][2]= formatInt(thisPop) if maplist.width >= 280 then captionTableText[argval][3]= yearVal else captionTableText[argval][3]='no-year' end if args['show-q-values'] == '1' then captionTableText[argval][4]='<small>[[wikidata:'..qvalue..'|'..qvalue..']]</small>' else local nbsp = '&nbsp;' if maplist.width < 230 then nbsp = ' ' end captionTableText[argval][4]='' if captionTableText[argval][3] == 'no-year' then captionTableText[argval][2] = captionTableText[argval][2]..'<small><sup>'..nbsp..'[[wikidata:'..qvalue..'|WD]]</sup></small>' else captionTableText[argval][3] = captionTableText[argval][3]..'<small><sup>'..nbsp..'[[wikidata:'..qvalue..'|WD]]</sup></small>' end end if args['mark-title'..argval] == 'none' then -- no title else args['mark-title'..argval] = args['mark-title'..argval]..' ('..plabel..' '..formatInt(thisPop)..areatxt..')' end if not args['mark-description'..argval] then args['mark-description'..argval] = (mw.wikibase.getDescription(qvalue) or '')..'<br>' end if (not args['mark-image'..argval]) and (mw.wikibase.getBestStatements(qvalue,'P18')[1]) then args['mark-image'..argval] = mw.wikibase.getBestStatements(qvalue,'P18')[1].mainsnak.datavalue.value end item = multiCheck(args,'shape',argval,'circle') --default to circle if geo-data if not string.find(item,'svgpath,') then item=string.lower(item) end -- ensure svgpath items retain original case values else item = multiCheck(args,'shape',argval,'image') if not string.find(item,'svgpath,') then item=string.lower(item) end -- ensure svgpath items retain original case values end if string.find(item,'n-',0,true)==1 or string.find(item,'l-',0,true)==1 then autoDotTag=string.sub(item,0,1) item=string.sub(item,3) end if item == 'image' then shapeResult[nval].shape = 'image:' shapeResult[nval].shapeFile =multiCheck(args,'mark',argval,'Red pog.svg') shapeWidth=-1 else shapeResult[nval].shape = item end item= multiCheck(args,'mark-size',argval,(shapeResult[nval].shapeSize or '14px')) -- add geo-data as fallback if already assigned local a,b,c= getsize(string.gsub(string.gsub(item,',','px')..'px','pxpx','px')) if b==a and args['mark-dim'..argval] then b= b / tonumber(string.match(args['mark-dim'..argval],"[%.%-?%d]+")) end shapeHeight=b/2 item=tostring(a)..'px'..tostring(b)..'px'..tostring(c)..'px' shapeResult[nval].shapeSize= item itemTab=splitItem(multiCheck(args,'shape-color',argval,'hard red'),2) shapeResult[nval].shapeColor=itemTab[1] or 'hardred' item=itemCheck(itemTab[2],'%') -- jump through the various opacity hoops and add to color if needed if not item then item=itemCheck(args['shape-opacity'..argval],'%') end if item and item~='0%' and item~='100%' then shapeResult[nval].shapeColor=shapeResult[nval].shapeColor..item end shapeResult[nval].shapeAngle=itemCheck(multiCheck(args,'shape-angle',argval,'0'),'deg') or '0deg' if args['shape-angle'..argval] then shapeResult[nval].shapeAngleArg=true else shapeResult[nval].shapeAngleArg=false end --sort out the outline entry itemTab=splitItem(multiCheck(args,'shape-outline',argval,'transparent,0,100,solid'),4) shapeResult[nval].outlineColor=itemTab[1] or 'dark grey' shapeResult[nval].outlineWidth=itemCheck(itemTab[2],'px') or '1px' if itemTab[3] and itemTab[3]~='100' and itemTab[3]~='0' then shapeResult[nval].outlineColor=shapeResult[nval].outlineColor..itemCheck(itemTab[3],'%') end shapeResult[nval].outlineStyle=itemTab[4] or 'solid' -- label size, background, outline shapeResult[nval].textAT=args['label-attribute'..argval] or '' itemTab=splitItem( multiCheck(args,'label-size',argval,'12'),3) shapeResult[nval].textSZ=itemCheck(itemTab[1],'px') or '12px' if itemTab[2]=='outline' then shapeResult[nval].textBG=itemTab[3] or 'transparent' shapeResult[nval].textOL='1px' elseif itemTab[3]=='outline' then shapeResult[nval].textBG=itemTab[2] or 'transparent' shapeResult[nval].textOL='1px' else shapeResult[nval].textOL='0px' shapeResult[nval].textBG=itemTab[2] or 'transparent' end if getColor(shapeResult[nval].textBG)==CTB['hardgrey'] and shapeResult[nval].textBG~='hardgrey' then shapeResult[nval].textBG= 'transparent' end --label color etc itemTab=splitItem(multiCheck(args,'label-color',argval, 'darkgrey','label-colour'),2) shapeResult[nval].textCL=itemTab[1] or 'darkgrey' if itemTab[2] and itemTab[2]~='0%' and itemTab[2]~='100%' then shapeResult[nval].textCL=shapeResult[nval].textCL..itemTab[2] end shapeResult[nval].textSP=itemCheck( multiCheck(args,'label-spacing',argval,'0'),'px') -- sets letter-spacing in px shapeResult[nval].textLH=itemCheck( multiCheck(args,'label-height',argval,'120'),'%') -- sets line height, 120% default shapeResult[nval].textNG=itemCheck(multiCheck(args,'label-angle',argval,'0'),'deg') if args['label-angle'..argval] then shapeResult[nval].textNGarg=true else shapeResult[nval].textNGarg=false end --sgd=dotTag attributes shapeResult[nval].tagSize=(args['geo-number-size'] or tostring(shapeHeight*1.5))..'px' local c1,c2=checkColors(shapeResult[nval].shapeColor) shapeResult[nval].tagColor=c2 shapeResult[nval].tagSpacer='0px' shapeResult[nval].tagAngle='0deg' -- sge extension line attributes local shapePos=splitItem(multiCheck(args,'label-pos',argval,'right'),6) if shapePos[2]=='with-line' or shapePos[2]=='n-line' then shapeResult[nval].textEW=(shapePos[3] or '1')..'px' -- width shapeResult[nval].textEC=(shapePos[4] or shapeResult[nval].shapeColor or 'darkgrey') elseif shapePos[2]=='photo-panel' then shapeResult[nval].textEW='2px' -- width shapeResult[nval].textEC=shapeResult[nval].textCL else shapeResult[nval].textEW='0px' -- width shapeResult[nval].textEC='grey'-- colour end shapeResult[nval].textES='solid' if argval=='H' then return dotResult end --Assign dot values local dotItem={} dotItem.group=nval dotItem.code=nval dotItem.posType=shapePos[2] or 'nil' if (shapePos[2] or '') =='photo-panel' then dotItem.ppwidth= tonumber(string.match((shapePos[4] or '110'),"%d+")) dotItem.ppheight= tonumber(string.match((shapePos[5] or '48'),"%d+") ) dotItem.photowidth=round(tonumber(string.match((shapePos[3] or '1.3'),"[%.%-?%d]+")) * dotItem.ppheight+1,0) dotItem.photoImage=args['mark-image'..argval] --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end if (shapePos[2] or '') =='mark-line' then local x=tonumber(argval or '1') dotItem.markDest=shapePos[6] or tostring(x-1) dotItem.mlWidth= tonumber(string.match((shapePos[3] or '1'),"%d+")) dotItem.mlStyle= shapePos[4] or 'solid' dotItem.mlGap=tonumber(string.match((shapePos[5] or '0'),"[%d]+")) shapeResult[nval].textEC=shapeResult[nval].outlineColor or 'darkgrey' end if args['mark-coord'..argval] then itemTab=splitItem(convertCoordsTrad (args['mark-coord'..argval]),2) if itemTab[1] == nil or itemTab[2] == nil then debugmsg(mw.addWarning('Unable to read coordinates for mark-coord'..argval)) else dotItem.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) or 0 end else dotItem.lat=tonumber(string.match(args['mark-lat'..argval],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(args['mark-lon'..argval],"[%.%-?%d]+")) or 0 end if args['dateline'..argval] and (args['dateline'..argval]=='1' or args['dateline'..argval]=='-1') then dotItem.lon=dotItem.lon+(tonumber(args['dateline'..argval] ) *360) end maplist.lon=dotItem.lon maplist.lat=dotItem.lat dotItem.gridx, dotItem.gridy = maptogrid(maplist,1) local item=args['mark-title'..argval] or '' -- sort out the caption, wikilink and plaintext tooltip items from dotLink if item=='none' then dotItem.param1='nomap nolist' item='' end if string.find(item,'nolist') then dotItem.param1='nolist,' item = string.sub(args['mark-title'..argval],8) or args['label'..argval] end dotItem.dotLink=item if item ~= '' then item=stripdivs(item) dotItem.title=delink({item}) local linkstart= string.find(item,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then dotItem.dlink=delink({string.sub(item,linkstart,string.find(item,']]',1,true)+1),wikilinks='target'}) -- debugmsg('dlink for '..item..' is '..dotItem.dlink) else dotItem.dlink='' end else dotItem.dlink='' dotItem.title='' end if autoDotTag=='n' then item=nval elseif autoDotTag=='l' then item=string.char(64+tonumber(nval)) else item='' end dotItem.dotTag = args['numbered'..argval] or item if shapePos[2]=='n-line' and (args['label'..argval] or args['label'..argval]=='') then if dotItem.dlink =='' then item=dotItem.dotTag..' '..args['label'..argval] else item='[['..dotItem.dlink..'|'..dotItem.dotTag..']] '..args['label'..argval] end else item=(args['label'..argval] or '') end if args['labela'..argval] then item = item..'^'..args['labela'..argval] end if args['labelb'..argval] then item = item..'^'..args['labelb'..argval] end local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end dotItem.labelText = a -- convert hats to line breaks if argval=='' then item = (args['label-offset-x']) or (args.ldx) or '0' else item=args['label-offset-x'..argval] or args['ldx'..argval] or args['label-offset-xD'] or args.ldxD or args['label-offset-x1'] or args.ldx1 or '0' end dotItem.dx=tonumber(string.match(item,"[%.%-?%d]+")) if argval=='' then item = (args['label-offset-y']) or (args.ldy) or '0' else item=args['label-offset-y'..argval] or args['ldy'..argval] or args['label-offset-yD'] or args.ldyD or args['label-offset-y1'] or args.ldy1 or '0' end dotItem.dy=tonumber(string.match(item,"[%.%-?%d]+")) dotItem.labelPos=shapePos[1] if args['mark-image'..argval] then dotItem.imageName= args['mark-image'..argval] end dotItem.info=args['mark-description'..argval] or '' table.insert(dotResult,1,dotItem) -- add to start of list, so they are in reverese order for displaying -- debugmsg(dotItem.code..' = '..dotItem.lat..','..dotItem.lon..': '..dotItem.labelText..' - '..dotItem.title..', '..shapeResult[nval].shapeSize) return dotResult end local function tradstyleParseShapes(args,dotTable,dotmax) local sgNumbers,sgSortable={},{} for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['geo-data'..x] and (not args['mark-coord'..x]) and (not args['mark-lat'..x]) then -- get lat and lon values from wikidata and put them into the args.lat and args.lon for use wherever needed if (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1]) then args['mark-lat'..x] = (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1].mainsnak.datavalue.value.latitude) or '0' args['mark-lon'..x] = (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1].mainsnak.datavalue.value.longitude) or '0' local p=args['geo-data-type'] or 'P1082' if mw.wikibase.getBestStatements(args['geo-data'..x],p)[1] then local n = tonumber(mw.wikibase.getBestStatements(args['geo-data'..x],p)[1].mainsnak.datavalue.value.amount) or 0 if p == 'P2046' then -- area, so convert to sq km if needed (/100) n = convertToSqKm( n, mw.wikibase.getBestStatements(args['geo-data'..x],p)[1].mainsnak.datavalue.value.unit) end geoTotalPop = geoTotalPop + n end else debugmsg(mw.addWarning('could not find coords for '..mw.wikibase.getLabel(args['geo-data'..x])..', '..args['geo-data'..x])) end end if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) then sgNumbers[x]=x -- add it to the list end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form (needs a second table, seemingly) if args['mark-coord'] or (args['mark-lat'] and args['mark-lon']) then table.insert(sgSortable,'0') end -- add it to the end of the list local default={} default.D={} local dotResult={} for k,sgnum in pairs(sgSortable) do -- work through the sorted list, parsing each set of shapes in turn, from 1 upwards shapeList[sgnum]={} dotTable=assignTradstyleShape(shapeList,default,dotTable,args,sgnum) end dotTable=assignTradstyleShape(shapeList,default,dotTable,args,'H') -- construct an extra highlight shapeitem local unitConvert=1 local areaUnits='' if args['geo-region'] and (args['find-geo-dots'] or '1') ~= '0' then local region = args['geo-region'] -- or mw.wikibase.getEntityIdForCurrentPage() local pvalue = args['geo-data-type'] or 'P1082' local plabel = mw.wikibase.getLabel(pvalue) local subhead='' -- Assemble the subhead content, a line of text about the region as a whole if region and mw.wikibase.getBestStatements(region,pvalue)[1] then local punit=mw.wikibase.getBestStatements(region,pvalue)[1].mainsnak.datavalue.value.unit:match("Q.*") -- extract Q value if pvalue == 'P10622' then -- add other currency-based pvalues here areaUnits=mw.wikibase.getLabel(punit) end if punit == 'Q35852' then unitConvert=100 areaUnits = ' '..sqkm -- if its hectares, convert to sq km elseif punit == 'Q232291' then unitConvert = (1/2.59) areaUnits = ' '..sqkm -- or this if it's square miles elseif punit == 'Q81292' then unitConvert = 247.1 areaUnits = ' '..sqkm -- or this if it's acres elseif punit == 'Q712226' then unitConvert = 1 areaUnits = ' '..sqkm -- or this if it's already sq km elseif punit == 'Q4917' then areaUnits=' US$' -- replace long version with some shortform currencies () elseif punit == 'Q4916' then areaUnits=' euro' elseif punit == 'Q25224' then areaUnits=' £GBP' end -- if pvalue == 'P2046' then -- may need to add other instances of sq km so add in other P-items here if they need units showing -- end geoTotalPop=(mw.wikibase.getBestStatements(region,pvalue)[1].mainsnak.datavalue.value.amount) / unitConvert local yearVal if mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers then yearVal = mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P585[1].datavalue.value.time if yearVal and yearVal:sub(1,1) == '+' then yearVal = yearVal:sub(2,5) if mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459 and mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459[1] then local methodVal = mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459[1].datavalue.value.id if methodVal then yearVal=yearVal..' '..mw.wikibase.getLabel(methodVal) end end yearVal = ' ('..yearVal..')' else yearVal ='' end end subhead=totalItems..' '..mw.wikibase.getLabel(region)..' '..plabel..' ([[wikidata:'..region..']]) = '..formatInt(round(geoTotalPop),1)..areaUnits..yearVal else if pvalue == 'P2046' then areaUnits = sqkm end subhead=totalItems..' '..mw.wikibase.getLabel(region)..' '..plabel..' ('..sumOfDatapoints..') = '..formatInt(round(geoTotalPop),1)..areaUnits end if maplist.width < 350 and #plabel > 4 then plabel=string.sub((plabel or ' '),1,3)..'.' end if maplist.width < 330 then numberCol = ' ' end if maplist.width < 280 then year = 'no-year' end captionTable = subhead if (args['show-q-values'] or '') ~= '1' then captionTable = captionTable..'<div style="float:right;"><small> (WD&nbsp;=&nbsp;'..wikidataLink..')</small></div><br>' wikidataLink='' end captionTable = captionTable..'<table class="wikitable plainrowheaders sortable mw-collapsible mw-collapsed" style="width: 100%;"><caption>'..(args.toggletext or toggletext)..' </caption>' captionTable = htmlTableLine(captionTable,numberCol,mw.wikibase.getLabel(region),plabel..' '..areaUnits, year, wikidataLink,'header') end return dotTable end local function checkfortooltip (title,dx,dy,dotlabel,dlink,nolabel) -- returns tlink if available, and dlink, if needed and tshape=true if shape needed local tshape,tlink = false,"" if dlink~='' and not nolabel then tlink=dlink end if (tlink=="" or nolabel) and title~="" then tshape=true end -- tshape flags True if title is wanted for shape if (not (dx==0 or dy==0) or dotlabel=='') and title~='' then tshape=true end -- add tooltip to shape if its number has moved return tshape,tlink end local function tshift(angle) -- adjustment to place text near the centre of a triangle, shifted to allow rotation of triangle shape local x=tonumber(string.match(angle,"%-?%d+")) if x<0 then x=360+x end -- set to a single degree direction, 0 to 360 if x>359 then return 0,0 end -- shift the centre of the triangle based on rotation value if x <=40 or x>=320 then return -0.17,0 -- triangle up= -shiftv elseif x>=140 and x<=220 then return 0.17,0 --triangle down= +shiftv elseif x >220 then return 0,-0.17 --triangle left= -shifth elseif x >40 then return 0,0.22 --triangle right= +shifth end return 0,0 end local function makeTriangle(result,row,shape,outline,tlink) local w,h,r=getsize(shape.shapeSize) if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) w=w+p*2 h=h+p*2 end table.insert(result,'<div ') if tlink then table.insert(result,' title="'..row.title..'" ') end table.insert(result,'style="display:inline-block; position: absolute') if shape.shapeAngle ~= '0deg' then table.insert(result,'; transform: rotate('..shape.shapeAngle..')') end local shiftv,shifth=0,0 shiftv,shifth=tshift(shape.shapeAngle) table.insert(result,'; top: '..tostring(row.gridy-h/2+h*shiftv)..'px') table.insert(result,'; left: '..tostring(row.gridx-w/2+w*shifth)..'px; width: 0; height: 0; outline-width: 0px') table.insert(result,'; border-left: '..tostring(w/2)..'px solid transparent') table.insert(result,'; border-right: '..tostring(w/2)..'px solid transparent') if outline then -- fill with outline colour, to make a 'base layer' or shape colour table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.outlineColor).. '">') else table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.shapeColor).. '">') end table.insert(result,'</div>') end local function makeSquare(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) if shape.shape=='square' then h=w end -- squares are always square!. box can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end if r~=0 then div:css('border-radius',tostring(r).."px") end if shape.shape=='panel' then div:css('top', tostring(row.gridy).."px") div:css('left', tostring(row.gridx).."px") else div:css('top', tostring(row.gridy-h/2).."px") div:css('left', tostring(row.gridx-w/2).."px") end div:css('width', tostring(w).."px") div:css('height', tostring(h).."px") div:css('background-color', getColor(shape.shapeColor) ) div:css('color', 'inherit') table.insert(result,tostring(div)) end local function makeCircle(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding if shape.shape=='circle' then h=w end -- circles are always round. ellipse can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end div :css('top', tostring(row.gridy-h/2).."px") :css('left', tostring(row.gridx-w/2).."px") :css('width', tostring(w).."px") :css('height', tostring(h).."px") :css('border-radius', "50%") :css('background-color', getColor(shape.shapeColor) ) :css('color', 'inherit') table.insert(result,tostring(div)) end local function makeRuleA(result,row,shape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local lineV=0 if shape.shape=='rulea' then lineV=oWid*3+16 end table.insert(result,"<div style=\"display:inline-block; position: absolute") -- create a square transparent container, which will rotate line and arrow together table.insert(result,"; top:"..tostring(row.gridy - w/2).."px") table.insert(result,"; left:"..tostring(row.gridx - w/2).."px") table.insert(result,"; width:"..tostring(w).."px") table.insert(result,"; height:"..tostring(w).."px; background:transparent; color:inherit;") table.insert(result,"; transform: rotate( "..tostring(tonumber(string.match(shape.shapeAngle,"[%.%-?%d]+")) - 90).."deg);\">" ) table.insert(result,"<div style=\"display:inline-block; position: absolute") -- put the line (as a border-right) across the container table.insert(result,"; top:"..tostring(lineV).."px") table.insert(result,"; left:"..tostring((w - oWid )/2).."px; width: 0px") table.insert(result,"; height: "..tostring(w -lineV).."px") table.insert(result,"; border-right: "..shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) table.insert(result,"; background:transparent; color:inherit;\"></div>") if shape.shape=='rulea' then table.insert(result,"<div style=\"display:inline-block; position: absolute; top: 0px") --and add arrow head table.insert(result,"; left:"..tostring(w/2-( oWid/2)-oWid*0.55-2).."px; width: 0; height: 0; outline-width: 0px") table.insert(result,"; border-left: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-right: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-bottom: "..tostring(oWid*3+16).."px solid "..getColor(shape.outlineColor).."\"></div>") end table.insert(result,"</div>") end local function makeCurveA(result,row,shape) -- draw a curve with Arrow ----- local w,h=getsize(shape.shapeSize) -- = width,height local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local Angle=tonumber(string.match(shape.shapeAngle,"[%.%d]+")) table.insert(result,'<div style="position: absolute;') --set up out div, which will allow the whole to rotate table.insert(result,'top:'..tostring(row.gridy - (w + oWid*3+16)/2)..'px;') table.insert(result,'left:'..tostring(row.gridx - ( w + oWid*3+16)/2)..'px; ') table.insert(result,'width: '..tostring(w+oWid*3+16)..'px; ') table.insert(result,'height: '..tostring(w+oWid*3+16)..'px; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate( '..tostring(Angle-120)..'deg);">') else table.insert(result,'transform: rotate( '..tostring(Angle -62)..'deg);">') end table.insert(result,'<div style="position: absolute;') --set up div for the rounded corner of a rectangle table.insert(result,'border-left: '..shape.outlineWidth..' '..shape.outlineStyle..' '..getColor(shape.outlineColor)..';') if shape.shape=='curvea' then table.insert(result,'border-radius: 10000px 0 0 '..tostring(w)..'px; top:0px; left:'..tostring(w*0.25)..'px;') else table.insert(result,'border-radius: '..tostring(w)..'px 0 0 10000px;') table.insert(result,'top:'..tostring((oWid*3+16)/2+w*0.15)..'px; left:'..tostring(w*0.25)..'px;') end -- and add a triangular arrow head table.insert(result,'width: '..tostring(w)..'px; height: '..tostring(w)..'px;"></div><div style="position: absolute; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate(180deg); top:'..tostring(w-1)..'px; ') else table.insert(result,'transform: rotate(0deg); top: '..tostring(0-( ( oWid*3+16)/2)+1+( w*0.15) )..'px;') end -- reverse table.insert(result,'left:'..tostring(0-( oWid*0.6)-2+(w*0.25))..'px;') table.insert(result,'width: 0; height: 0; outline-width: 0px; border-left: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-right: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-bottom: '..tostring(oWid*3+16)..'px solid '..getColor(shape.outlineColor)..';"></div></div>') end local function makeLineTo (result,x1,y1,x2,y2,oWid, oStyle, oCol,double,shape) table.insert(result,"<div style=\"display:inline-block; position: absolute;") -- draw a line between x1,y1 and x2,y2, px-coords where 0,0 is centre of frame -- Maths calculations thanks to ES local tw if double <= 1 then tw = tonumber(string.match(oWid,"[%.%d]+"))/2 -- shift centre by half the line width else tw = tonumber(string.match(oWid,"[%.%d]+")) + (double/2) - 0.5 -- shift centre by a line width + half the gap end -- debugmsg(oWid..'+'..double..'/2='..tw) table.insert(result,"left: "..tostring(x1+( (x2-x1)/2) - (math.sqrt( ( x2-x1)^2 + (y2-y1)^2 )/2) ).."px;") table.insert(result,"top: "..tostring(y1+( ( y2-y1 )/2) - tw).."px;") table.insert(result,"width: "..tostring(math.sqrt( (x2-x1 )^2 + ( y2-y1 )^2) ).."px;") table.insert(result,"height: "..tostring(double).."px; background-color:transparent; color:inherit; ") table.insert(result,"outline-width: 0; border-bottom: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) if double>1 then table.insert(result,"border-top: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) end local lineAngle=math.atan(( y2-y1)/( x2-x1 ) )*180/math.pi -- if x1 == x2 then lineAngle=90 end table.insert(result,"transform: rotate("..lineAngle.."deg);\"></div>") -- debugmsg('text angle started as '..shape.textNG..' and lineAngle = '..lineAngle) if (lineAngle < -90) or (lineAngle >90) then lineAngle=lineAngle-180 end if shape and not shape.textNGarg then shape.textNG=tostring(round(lineAngle,0))..'deg' end -- Put in line angle for a default text angle as well -- if (y2 >= y1) and (x2 <= x1) then lineAngle=lineAngle-180 debugmsg('arrow angle corrected') end if x2 < x1 then lineAngle=lineAngle-180 end if shape and not shape.shapeAngleArg and shape.shape == 'arrowhead' then shape.shapeAngle=tostring(round(lineAngle,0))..'deg' end -- line angle for arrowhead -- debugmsg('text angle is now '..shape.textNG..'with arrow at '..lineAngle) end local function makeClipPath(result,row,shape,outline,tshape) --tshape is a flag to show if the tooltip (title=) is wanted -- return the text css div code to position and draw a shape occupying a specified clippath if string.find(shape.shape, 'svgpath,') and #shape.shape > 9 then pathshape.svgpath=string.sub(shape.shape,9) shape.shape='svgpath' end if not pathshape[shape.shape] then debugmsg(mw.addWarning('shape'..row.code..' = '..shape.shape..'. The shape name is not defined')) return end local w,h,r=getsize(shape.shapeSize) if shape.shape == 'arrowhead' then -- enlarge arrowheads, as they only go to the middle of the notional shape-size w=w*2.5 h=h*2.5 end if string.match(shape.shape,"circle") or string.match(shape.shape,"square") then h=w end -- use ellipse and box for stretched shapes local shifth,shiftv = 0,0 if string.match(shape.shape,"triangle") then shiftv,shifth =tshift(shape.shapeAngle) end if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) or 0 w=w+p*2 h=h+p*2 end table.insert(result,"<div ") if tshape then -- Add tooltip if needed table.insert(result," title=\""..row.title.."\" ") end table.insert(result,"style=\"display:inline-block; position: absolute; background-color:") if outline then table.insert(result,getColor(shape.outlineColor)) -- fill with outline colour, to make a 'base layer' else table.insert(result,getColor(shape.shapeColor)) end table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;"..pathshape[shape.shape].."&#39;) ") -- adds the required clippath data from the table of pathshape string literals table.insert(result,"; top:"..tostring(row.gridy - 10 + h*shiftv).."px") table.insert(result,"; left:"..tostring(row.gridx - 10 + w*shifth).."px") table.insert(result,"; width:20px") -- needs to be a path within a 20px20px box, and then rescales using size values to match other shape sizes table.insert(result,"; height:20px; transform:") if shape.shapeAngle ~= "0deg" then table.insert(result," rotate("..shape.shapeAngle..")") end table.insert(result," scale("..tostring(w/16)..", "..tostring(h/16)..")") table.insert(result,"\"></div>") end local function makeImage(result,row,shape) local w,h,r=getsize(shape.shapeSize) local image=shape.shapeFile if not image or image=='' then image='Red pog.svg' end local imagediv=mw.html.create ('div') imagediv:css('position', "absolute") if shape.shapeAngle ~= "0deg" then imagediv:css('transform',"rotate("..shape.shapeAngle..")") end imagediv :css('top', (row.gridy+2 + math.min(h/2-12,0) - h/2).."px") --File seems to adjust pos for small images :css('left', (row.gridx-1-w/2).."px") :css('background-color', "transparent" ) :css('color','inherit') :wikitext('[[file:'..image..'|'..tostring(w+2)..'px|alt='..(row.title or '')..'|link=]]') table.insert(result,tostring(imagediv)) end local function makePhotoPanel(result,row,shape) local h=row.ppheight table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;' ) table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') table.insert(result, 'width: '..tostring(row.ppwidth)..'px; height: '..tostring(h)..'px; border-radius: 2px; color:inherit;') table.insert(result, 'background-color: #E8E8D6; outline: 2px solid '..getColor(shape.textCL)..'; box-shadow: 2px 2px 4px #33203335;"></div>') if row.photoImage and row.photowidth >0 then table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;') if row.labelPos=='left' or string.find(row.labelPos,'west') then table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') row.dx=row.dx+row.photowidth/2 else table.insert(result, 'left: '..tostring(row.gridx+row.dx + (row.ppwidth-row.photowidth) - row.ppwidth/2)..'px;') row.dx=row.dx-row.photowidth/2 end table.insert(result, 'background-color:transparent; color:inherit; border-radius: 2px;">') table.insert(result,'[[File:'..row.photoImage..'|x'..tostring(h)..'px|File:'..row.photoImage..']]</div>') row.labelPos='center' end end local function makePanelText(result, row, shape) local w,h,r=getsize(shape.shapeSize) local ty=tonumber(string.match(shape.textSZ or '11',"%d+") ) table.insert(result,'<div style="position:absolute; line-height: 120%; font-size: '..shape.textSZ..'; color:'..getColor(shape.textCL)..';') if row.labelPos == 'left' or string.find(row.labelPos,'west') then table.insert(result,'top: '..tostring(row.dy+row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+3)..'px; text-align: left; width:'..tostring(w)..'px;') elseif row.labelPos == 'right' or string.find(row.labelPos,'east') then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+w-3)..'px; text-align: right; width: max-content; transform: translateX(-100%);') elseif row.labelPos == 'top' or row.labelPos== 'north' then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') elseif row.labelPos == 'bottom' or row.labelPos=='south' then local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*1.1 table.insert(result,'top: '..tostring(row.gridy+w-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') else -- center or centre local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*0.6 table.insert(result,'top: '..tostring(row.gridy+(h/2)-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') end if shape.textSP and shape.textSP ~='0px' then table.insert(result,"letter-spacing: "..shape.textSP..';') end if shape.textLH and shape.textLH ~='120%' then table.insert(result,"line-height: "..shape.textLH..';') end table.insert(result,"vertical-align: middle;\">"..row.labelText.."</div>") end local function makeTextItem(result, row, shape, align, tlink, textItem, dotItem) local w,h,r=getsize(shape.shapeSize) table.insert(result,"<div ") if row.title ~= "" then table.insert(result," title=\""..row.title.."\" ") end local ty,bry,linkoffset = 0,0,0 local compy=0 local lh=tonumber(string.match(shape.textLH or '120',"%d+"))/100 if dotItem==1 or (dotItem==2 and row.posType~='n-line') then -- if there is a dotTag in the middle of the shape then use the tag settings ty=tonumber(string.match(shape.tagSize,"%d+")) or 0 table.insert(result,"style=\"position:absolute; line-height: 120%; top: "..tostring(row.gridy-ty*0.62)) table.insert(result,"px; left: "..tostring(row.gridx).."px; width: fit-content; ") table.insert(result,"text-align: center; color: "..getColor(shape.tagColor).."; background-color: transparent;") local trf="" if shape.tagAngle ~="0deg" then trf=" rotate("..shape.tagAngle..")" end table.insert(result, "transform: translateX(-50%)"..trf.."; font-size: "..shape.tagSize..";") if shape.tagSpacer~='0px' then table.insert(result, "letter-spacing:"..shape.tagSpacer..";") end else -- or add tfx settings for left, right or center align, colors, backgrounds, border-outline table.insert(result,'style="position:absolute; ') if dotItem==2 then -- dotTag is out at x,y so 85% ty=tonumber(string.match(shape.tagSize or '0',"%d+")) table.insert(result,'font-size: '..shape.tagSize..'; padding:0px 2px;line-height: 85%; top: '..tostring(row.dy+row.gridy-ty*0.52)) else -- it is labelText, so use textLH or 120% ty=tonumber(string.match(shape.textSZ or '11',"%d+")) if row.labelPos=='northwest' or row.labelPos=='northeast' then compy=-ty elseif row.labelPos=='southeast' or row.labelPos=='southwest' then compy=ty/2 end if row.labelPos and not (row.labelPos== 'auto' or row.labelPos=='') then bry=(select(2, string.gsub(row.labelText,"<br>", ""))*lh) -- is it a multiline text? expand by line-height /120%? if row.posType == 'with-line' then bry=0 end if row.labelPos=='bottom' or row.labelPos == 'south' then bry = 0 -- and shift by none, all or half elseif row.labelPos=='top' or row.labelPos == 'north' then if shape.shape=='image:' then bry= 1 + math.min(w/2-10,0)-bry*ty else bry= -bry*ty+2 end else bry=-bry*(ty/2 * lh) end end if row.posType == 'photo-panel' then bry=bry+3 end table.insert(result,'font-size: '..shape.textSZ..'; padding:0px 3px;line-height: '..shape.textLH..'; ') table.insert(result,'top: '..tostring(row.dy+row.gridy+compy+bry-ty*lh/2)) end table.insert(result,"px; left: "..tostring(row.dx+row.gridx).."px; color: "..getColor(shape.textCL).."; ") table.insert(result,"width: max-content; ") local trf="" if shape.textNG ~="0deg" then trf="rotate("..shape.textNG..")" end if shape.textOL~="0px" then table.insert(result,"background-color: "..getColor(shape.textBG).."; ") table.insert(result,"border: "..shape.textOL.." solid "..getColor(shape.textCL).."; border-radius:6px;") else table.insert(result,"background-color: transparent;") end if row.labelPos=="right" or string.find(row.labelPos,'east') then table.insert(result,"text-align: left; ") linkoffset=w if shape.textNG ~="0px" then table.insert(result,"transform-origin: left; transform: rotate("..shape.textNG.."); ") end elseif row.labelPos=="left" or string.find(row.labelPos,'west') then if shape.textNG ~="0px" then table.insert(result,"transform-origin: right;") end linkoffset=-w table.insert(result,"text-align: right; transform: translateX(-100%) "..trf.."; ") else table.insert(result,"text-align: center; transform: translateX(-50%) "..trf.."; ") end table.insert(result,'font-weight: normal; line-height: '..shape.textLH..'; letter-spacing:'..shape.textSP..'; vertical-align: bottom;') end if string.find(shape.textAT or '','bold') then textItem='<b>'..textItem..'</b>' end if string.find(shape.textAT or '','italic') then textItem='<i>'..textItem..'</i>' end if shape.textOL=='0px' and shape.textBG~='transparent' and dotItem==0 then table.insert(result,'\"><span style=\"background-color: '..getColor(shape.textBG)..'; color:inherit;\">'..textItem..'</span></div>') else table.insert(result,"\">"..textItem.."</div>") end if tlink~='' and not string.match(row.param1 or "","nolink") then table.insert(result,makeLinkBox(row.gridx+row.dx+linkoffset, row.gridy+row.dy+bry, 16, row.dotTag..' '..row.title,tlink)) end end local function getshapetable(row,shape) -- Construct CSS divs for a dot from shape and map data local result={} local w,h,rndg=getsize(shape.shapeSize) local tshape,tlink=checkfortooltip(row.title,row.dx,row.dy,row.dotTag,row.dlink,string.match(row.param1 or "","nolink") ) local align=row.labelPos or '' local offsetx,offsety=0,0 local ty=tonumber(string.match((shape.tagSize or 9),"%d+" )) if row.labelText and row.labelText~='' then ty=tonumber(string.match((shape.textSZ or 11),"%d+" )) else align='center' end -- it is just for dotTag, so justify center -- identify align value and extend offsets local widthzone,heightzone = (w/2)+1,(h/2)+1 local theta,r = math.deg( math.atan2(row.dy, row.dx)) , math.sqrt(row.dx^2 + row.dy^2) if align=='auto' or align=='' then if (theta < -112 or theta > 112) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "left" offsetx=1 elseif (theta > -68 and theta < 68) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "right" offsetx=-1 elseif theta <0 then offsety=ty/2-1 -- bottom else offsety=0-ty/2+1 -- top end elseif align=='left' or string.find(align,'west') then row.dx=row.dx - w/2 widthzone=4 offsetx=-1 elseif align=='right' or string.find(align,'east') then row.dx=row.dx + w/2 widthzone=4 offsetx=1 elseif align=='top' or align=='north' then if string.find(shape.shape,'curve') then row.dy=row.dy-(h/2)-12 heightzone=4 offsety=ty/2-1 else row.dy=row.dy - h-2 heightzone=4 offsety=ty/2-1 end elseif align=='bottom' or align=='south' then if string.find(shape.shape,'curve') then row.dy=row.dy+(h/2) heightzone=4 offsety=ty/2-1 else row.dy=row.dy + h+2 heightzone=4 offsety=0-ty/2-1 end end if shape.textEW ~= "0px" and not string.match(row.param1 or "","noline") then -- debugmsg('r-(w/2) > widthzone r-w/2='..tostring(r-(w/2))..', widthzone='..widthzone..', label='..(row.labelText or '')..', postype='..(row.posType or '')) makeLineTo(result, row.gridx+1, row.gridy-1, row.gridx+row.dx+offsetx, row.gridy+row.dy+offsety, shape.textEW, shape.textES,shape.textEC,1) -- debugmsg('n-line for '..row.code..':'..tostring(row.gridx+1)..', '..tostring(row.gridy-1)..', '..tostring(row.gridx+row.dx+offsetx)..', '..tostring(row.gridy+row.dy+offsety)..', with width '..tostring(shape.textEW)..'px '..(shape.textES or 'no style')..' and color '..shape.textEC) end if row.posType and row.posType=='mark-line' then if row.gridx2 and row.gridy2 then makeLineTo(result, row.gridx, row.gridy, row.gridx2, row.gridy2, tostring(row.mlWidth)..'px', row.mlStyle, getColor(shape.textEC),row.mlGap,shape) -- debugmsg('mark-line line drawn from '..row.code..' with width '..tostring(row.mlWidth)..'px '..(row.mlStyle or 'no style')..' and color '..getColor(shape.textEC)) end end if w ~= 0 then if shape.shape=='itriangle' then shape.shape='triangle' shape.shapeSize=tostring(w)..'px'..tostring(w/2)..'px' end if shape.shape=="triangle" then if shape.outlineWidth ~= "0px" then makeTriangle(result,row,shape,true,false) -- larger triangle to give the outline, if required end makeTriangle(result,row,shape,false,tshape) -- smaller triangle to fit over the top elseif shape.shape=="square" or shape.shape=="box" or shape.shape=='panel' then makeSquare(result,row,shape,tshape) elseif shape.shape=="circle" or shape.shape=="ellipse" then makeCircle(result,row,shape,tshape) elseif string.find(shape.shape,'image:')==1 then makeImage(result,row,shape) elseif shape.shape=="rulea" or shape.shape=='rule' then makeRuleA(result,row,shape) elseif shape.shape=="curvea" or shape.shape=="curvec" then makeCurveA(result,row,shape) else -- use a pathshape clipPath if shape.outlineWidth ~= "0px" then makeClipPath(result,row,shape,true,false) -- larger path-shape to give the outline, if required end makeClipPath(result,row,shape,false,tshape) end end if row.ppwidth and row.ppwidth>0 then makePhotoPanel(result,row,shape) end if shape.shape=='panel' and row.labelText then makePanelText(result, row, shape) else if row.dotTag and row.dotTag ~= "" then -- there is a dotTag if (row.dx==0 and row.dy==0) and w>0 then -- it is on the dot so if dotsize is not 0 any label is ignored makeTextItem(result, row, shape, align, tlink, '<b>'..row.dotTag..'</b>', 1) else if row.labelText and row.labelText~='' then -- tag and label both used makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.labelText, 0) else makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.dotTag, 2) -- tag is ouside the dot end end else if (row.labelText and row.labelText~='') then -- just the label. No tag makeTextItem(result, row, shape, align, tlink, row.labelText, 0) end end end if tlink and tlink~='' then table.insert(result,makeLinkBox(row.gridx, row.gridy,w+3,row.dotTag..' '..row.title,tlink)) end return table.concat(result) end local function getmapframecontent(args,use) local result, comma= {}, '' local propertyTable= {} local color=splitItem(string.lower( args['map-data-color'] or 'dark orange'), 2) if use == 'basemap' then table.insert(result, '[') propertyTable.stroke = "#000000" propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') if color[2] then propertyTable['stroke-opacity'] = tonumber(color[2] or '20')/100 end else propertyTable.title = args['map-data-text'] or args['map-data'] or '' propertyTable.stroke = getColor(color[1]) propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') propertyTable['stroke-opacity'] = tonumber(color[2] or '100')/100 end if args['map-data-inverse'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geomask', ids = args['map-data-inverse'], properties = {title = (args['map-data-text'] or ''), stroke = '#555555', fill = '#555555', ['fill-opacity'] = 0.1, ['stroke-width'] = 1, ['stroke-opacity'] = 0.5 } } table.insert(result, mapJson) comma=', ' end if args['map-data-heavy'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-heavy'], properties = { title = args['map-data-heavy'], stroke = propertyTable.stroke, ['stroke-width'] = 9, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.2 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data-light'] and args['map-data-light'] ~= '' then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-light'], properties = { title = args['map-data-light'], stroke = propertyTable.stroke, ['stroke-width'] = 3, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.4 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data'] then propertyTable['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.2 local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data'], properties = propertyTable } table.insert(result, comma .. mapJson) comma=', ' end if args['map-wdqs'] then -- inserts a SPARQL wdqs query to find geopoints, geoshapes etc from wikidata local mapSparql = mw.text.jsonEncode{ type = 'ExternalData', service = args['map-wdqs-type'] or 'geopoint', query = args['map-wdqs'], } table.insert(result, comma .. mapSparql) -- debugmsg('mapSparql = ' .. mapSparql) comma=', ' end if args['map-raw'] then table.insert(result, comma .. args['map-raw']) end if use=='basemap' then table.insert(result,']') end return table.concat(result) end --eg | minilocator=filename,bottom right,132px153px, 38%,60%, 22px local function makeLocatorMap (args, result) local miniFile,pos,itemlist,miniW,miniH, miniX,miniY,miniBox, miniBH if args['mini-locator'] then pos,miniFile=extractItem(args['mini-locator']) -- first item is filename, in quotes if it includes commas itemlist=splitItem(pos,5) -- put items in a table filename removed, position,WpxHpx, x%y%,box pos=itemlist[2] or 'right' miniW,miniH=getsize(itemlist[3]) miniX=tonumber(string.match(itemlist[4] or '0','[%d]+'))*miniW/100 miniY=tonumber(string.match(itemlist[5] or '0','[%d]+'))*miniH/100 miniBox=tonumber(string.match(itemlist[6] or '0','[%d]+')) miniBox=miniBox*miniW/100 miniBH=miniBox * maplist.height/maplist.width elseif args['mini-file'] then miniFile = args['mini-file'] pos=string.lower(args.minimap or 'right') if pos=='file' then pos='right' end miniW,miniH = tonumber(args['mini-width'] or 60), tonumber(args['mini-height'] or 60) -- find top left corner of locator miniBox,miniBH=tonumber(args['minimap-boxwidth'] or '0'),0 -- firm up pos offsets for dot (with % or not) and boxsize if any, miniX, miniY=args['minipog-gx'],args['minipog-gy'] if not miniX then miniX=tonumber(args['minipog-x'] or '0') else miniX=tonumber(miniX)*tonumber(miniW)/100 end if not miniY then miniY=tonumber(args['minipog-y'] or '0') else miniY=tonumber(miniY)*tonumber(miniH)/100 end if args['minipog-gx'] then miniBox=miniBox*miniW/100 end miniBH=miniBox * maplist.height/maplist.width else return end local miniTop,miniLeft=0,1 if not string.find(pos,'top') then --only use top left, as link box is in top right or put bottom left or right miniTop=maplist.height+2-miniH if string.find(pos,'right') then miniTop=miniTop-15 -- to avoid (c) line miniLeft=maplist.width-1-miniW end end table.insert(result,'<div style="position: absolute; outline-width: 1px; outline-style: solid; outline-color: white;') table.insert(result,'top: '..tostring(miniTop)..'px; left:'..tostring(miniLeft)..'px; width:'..tostring(miniW)..'px;' ) table.insert(result,'background-color:transparent; color:inherit;">[[File:'..miniFile..'|'..tostring(miniW)..'px|File:'..miniFile..']]</div>') if miniX and miniX>0 then if args['minipog-y'] then miniY=miniY/1.04 end if args['minipog-x'] then miniX=miniX/1.04 end if miniBox<1 then table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-3-6)..'px; left:'..tostring(miniX+miniLeft-3)..'px;') table.insert(result, 'width: 6px; background-color:transparent; color:inherit;">[[File:Red pog.svg|6px|link=]]</div>') else table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-miniBH/2)..'px; left:'..tostring(miniX+miniLeft-miniBox/2)..'px;') table.insert(result, 'width: '..tostring(miniBox)..'px; height:'..tostring(miniBH)..'px; outline:1px solid #AA1205; background-color:#AA120522; color:inherit;"></div>') end end end local function makeArcText(args,result,nval) local items, itemlist='',{} local arcText='' if args['arc'..nval] then items=convertCoords (args['arc'..nval]) items,arcText=extractItem(items) -- first item is text, in quotes if it includes commas itemlist=splitItem(items,9) -- put items in a table: text, lat,lon,size,color,angle,gap,radius,ellipse if itemlist[4]=='' then itemlist[4]='12' end itemlist[4]=string.match((itemlist[4] or '12'),"[%.%-?%d]+") if itemlist[5]=='' then itemlist[5]='grey' end itemlist[6]=string.match((itemlist[6] or '0'),"[%.%-?%d]+") itemlist[7]=string.match((itemlist[7] or '0'),"[%.%-?%d]+") itemlist[8]=string.match((itemlist[8] or '0'),"[%.%-?%d]+") end if args['arc-coord'..nval] then local itemTab=splitItem(convertCoordsTrad (args['arc-coord'..nval]),2) maplist.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) maplist.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) else maplist.lat=tonumber(string.match(args['arc-lat'..nval] or itemlist[2] or '0',"[%.%-?%d]+")) maplist.lon=tonumber(string.match(args['arc-lon'..nval] or itemlist[3] or '0',"[%.%-?%d]+")) end local arcX,arcY=maptogrid(maplist,6) arcText = args['arc-text'..nval] or arcText local fontSize =tonumber(args['arc-text-size'..nval] or itemlist[4] or '12') local textColor=getColor(string.gsub(args['arc-text-color'..nval] or itemlist[5] or 'grey','[%s]+','') ) local arcAngle= tonumber((args['arc-angle'..nval]) or (itemlist[6]) or '45')-90 local arcRadius =tonumber(args['arc-radius'..nval] or itemlist[8] or '0.05') local arcGap = tonumber(args['arc-gap'..nval] or itemlist[7] or '1')* ( ( math.sin(8-math.rad(arcRadius))^8 )+0.4 )*( ( fontSize+6 )/15 ) arcRadius=450*arcRadius*0.75 local ellipseFactor=tonumber(args['ellipse-factor'..nval] or itemlist[9] or '1') local arcRotate =arcAngle+90 if arcGap<0 then arcRotate=arcAngle-90 end local latF=arcY - fontSize + (0-(math.sin(math.rad(arcAngle))) * arcRadius) local lonF=arcX - fontSize +(0-(math.cos(math.rad(arcAngle))) * arcRadius) local step=1 for codepoint in mw.ustring.gcodepoint( arcText ) do -- block step=1,#arcText do table.insert(result,'<div style="position: absolute;') local posY=tostring(round( (latF + (math.sin(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius)) *ellipseFactor,2))..'px;' local posX=tostring(round( (lonF + (math.cos(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius))/ellipseFactor,2))..'px;' table.insert(result,' top: '..posY..' left: '..posX..' transform: rotate( '..tostring(round(arcRotate +((step-1)*arcGap)),2)..'deg);') table.insert(result,'width:'..tostring(fontSize*2)..'px; text-align: center; background-color:transparent; color: '..textColor..';') table.insert(result,'vertical-align: baseline; font-size: '..tostring(fontSize)..'px;">'..mw.ustring.char(codepoint)..'</div>') step=step+1 end end local function makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) local item={} itemdescription=stripdivs(itemdescription or '') local templon=lon if lon > 180 then templon=lon-360 end --for hemisphere+ or -1 dots if lon < -180 then templon=lon+360 end -- use 'real' coordinates for geohack label, while retaining shifted coords for plot if itemcolor=='transparent' then itemcolor='white' end itemcolor=getColor(itemcolor) -- ensure no opacity, which breaks maplink if string.find(itemcolor,'#')==1 and #itemcolor>7 then itemcolor=string.sub(itemcolor,1,7) end table.insert(item, '{ "type": "Feature", "properties": {') table.insert(item, ' "title": "'..itemtitle..'",') table.insert(item, ' "description": "'..itemdescription) table.insert(item, ' ([https://geohack.toolforge.org/geohack.php?params='..tostring(lat)..';'..tostring(templon)) table.insert(item, '_dim:2000 '..tostring(lat)..','..tostring(templon)..'])",') table.insert(item, ' "marker-symbol": "-number-'..string.gsub(group,'%W','')..'", "marker-size": "medium", "marker-color": "'..itemcolor..'" },') table.insert(item, ' "geometry": {"type": "Point", "coordinates": ['..tostring(lon)..','..tostring(lat)..'] } }') return table.concat(item) end local function makeLegendBox(result,args) local legend ={} local line,count, maxWidth='',1,8 local item line,item=extractItem(args.legendBox or '') local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end legend.Text = a -- convert hats to line breaks line=splitItem(line,6) -- (text, size, poition,background color, text/outline color, param options) legend.Size=line[2] or '150px80px1px' legend.Pos=line[3] or '10px10px' legend.Background=line[4] or 'beigeground' legend.Color=line[5] or 'darkbrown' legend.Param= line[6] or '' local argnum,legendCount,titleHeight='1',1,0 if (legend.Text and legend.Text~='') then titleHeight=15+(13.4*(select(2, string.gsub(legend.Text,"<br>", "")))) end local legendLine,legendGroup,legendY={},{},{} while args['legendItem'..argnum] do -- assign legendLine, legendGroup, legendY for each dot line,legendLine[legendCount] = extractItem(args['legendItem'..argnum] or '') line=splitItem(line,3) legendGroup[legendCount]=line[2] or argnum if shapeList[legendGroup[legendCount]] then maxWidth=math.max(tonumber(string.match(shapeList[legendGroup[legendCount]].shapeSize or '10','[%d]+')),maxWidth) else maxWidth=math.max(tonumber(string.match(shapeList['1'].shapeSize or '10','[%d]+')),maxWidth) end maxWidth=maxWidth+1 if line[3] then legendY[legendCount]=tonumber(string.match(line[3],'[%d]+')) else legendY[legendCount] = 3+maxWidth*(legendCount-1)+titleHeight -- if (legend.Text and legend.Text~='') then legendY[legendCount]=legendY[legendCount]+15 end end legendCount=legendCount+1 argnum=tostring(legendCount) end local w,h,r=getsize(legend.Size or '') local x,y=getsize(legend.Pos or '') local div=mw.html.create ('div') div:css('position', 'absolute') div:css('outline', '1px solid'..getColor(legend.Color)) if r~=0 then div:css('border-radius',tostring(r).."px") end div :css('top', y.."px") :css('left', x.."px") :css('width', w.."px") :css('height', h.."px") :css('line-height','105%') :css('background-color', getColor(legend.Background) ) :css('color','inherit') if not string.find(legend.Param,'noshadow') then div:css('box-shadow', '2px 2px 4px #33203335') end div:tag( 'div' ) :css('position', 'absolute') :css('top','1px') :css('left', (w/2).."px") :css('width',(w-8)..'px') :css('text-align', 'center') :css('color', getColor(legend.Color)) :css('transform', 'translateX(-50%)') :css('font-size','11px') :wikitext(legend.Text) :done() for lct=1,legendCount-1 do --local t=legendGroup[lct] local shape=shapeList[legendGroup[lct]] or shapeList['1'] local row={} row.gridx=3+maxWidth/2 row.gridy=(legendY[lct] or 0) + 5 if shape.shape=='image:' then row.gridy=row.gridy+6 end row.dx=0 row.dy=0 local legendShape= getshapetable(row,shape) div:wikitext(legendShape) :tag( 'div' ) :css('position', 'absolute') :css('top',(legendY[lct] or 0)..'px') :css('left', (maxWidth+6)..'px') :css('width', (w-maxWidth-6)..'px') :css('text-align', 'left') :css('line-height','103%') :css('color', getColor(legend.Color)) :css('font-size','10px') :wikitext(legendLine[lct]) :done() end div:allDone() table.insert(result,tostring(div)) end local function getQset (qValue,pValue,argNumber,args) local result={} local sortType=string.lower(args['geo-list-sort'] or 'a') local testValue='0' if sortType == 'alpha' or sortType == 'a' or sortType == 'ah' or sortType == 'al' then testValue = mw.wikibase.getLabel(qValue) elseif sortType == 'value' or sortType == 'v' or sortType == 'vh' or sortType == 'vl'then if qValue==nil or pValue == nil then debugmsg(mw.addWarning((qValue or 'nil')..' Qnumber for '..(pValue or 'nil'))) else if mw.wikibase.getBestStatements(qValue,pValue)[1] then testValue = mw.wikibase.getBestStatements(qValue,pValue)[1].mainsnak.datavalue.value.amount or '0' if pValue == 'P2046' then testValue = convertToSqKm(testValue, mw.wikibase.getBestStatements(qValue,pValue)[1].mainsnak.datavalue.value.unit) end end end elseif sortType == 'north' or sortType == 'n' or sortType == 'nh' or sortType == 'nl' then if mw.wikibase.getBestStatements(qValue,'P625')[1] then testValue = mw.wikibase.getBestStatements(qValue,'P625')[1].mainsnak.datavalue.value.latitude or 0 end elseif sortType == 'west' or sortType == 'w' or sortType == 'wh' or sortType == 'wl' then if mw.wikibase.getBestStatements(qValue,'P625')[1] then testValue = mw.wikibase.getBestStatements(qValue,'P625')[1].mainsnak.datavalue.value.longitude or 0 end end result[1]=testValue result[2]=argNumber return result end function p._main ( args ) local result={} local frame=mw.getCurrentFrame() local dotTable={} local magVal,scaleVal = args.magnify,'' local origH,origW=maplist.height,maplist.width if magVal then --set up the values needed to magnify the top-right portion of the map magVal=tonumber(string.match(magVal or '0',"[%.%-?%d]+")) or 1 if magVal>1 and magVal <=2 then maplist.height= round(maplist.height/magVal,0) maplist.width=round(maplist.width/magVal,0) scaleVal='transform: scale('..magVal..') translateY('..tostring((origH-maplist.height)/2)..'px);' else magVal=1 end end -- set up the three nested div boxes (plus an extra if centered) to put the map plus title/caption area, in an appropriate frame on the page if args.float=='center' or args.float=='centre' then table.insert(result,'<div class="center"><div class="thumb tnone">') elseif args.float=='left' then table.insert(result,'<div class="thumb floatleft">') else table.insert(result,'<div class="thumb floatright">') end table.insert(result,'<div class="thumbinner" style="position: relative; top: 0px; right: 0px; width: '..(args.width or "400")..'px;">') if args.title then table.insert(result,'<div class="center" style="font-weight:bold">'..args.title..'</div>') end if magVal and magVal >1 and magVal<=2 then table.insert(result,'<div class="thumbinner noresize" style="display:block; position: relative; outline:0px; border:0px; padding:0px; background-color:transparent; color:inherit;') table.insert(result,'top: 50%; right: '..tostring(0-((origW-maplist.width))/2)..'px; ') table.insert(result,'height: '..origH..'px; width: '..maplist.width..'px; '..scaleVal..'">') else magVal=1 table.insert(result,'<div class="thumbinner noresize" style="position: relative; outline:0px; border:0px; padding:0px;') table.insert(result,'top: 0px; right: 0px; ') table.insert(result,'height: '..maplist.height..'px; width: '..maplist.width..'px ">') end --add any boundary Qvalues to map-data-light if requested if args['geo-region'] and (args['geo-boundaries'] == '1') then local qmaps=args['map-data-light'] or '' if qmaps ~='' and string.sub(qmaps,#qmaps) ~= ',' then qmaps=qmaps..',' end local geoRegion = args['geo-region'] -- or add base-page here local geoSubRegion = args['geo-sub-region'] or 'P150' if not mw.wikibase.isValidEntityId(geoSubRegion) then geoSubRegion = 'P150' end if mw.wikibase.isValidEntityId(geoRegion) then local ct=1 local qtemp while (mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct]) do -- debugmsg('table '..ct..' - '..mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id) qtemp = mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id or nil if qtemp then if mw.wikibase.entityExists(qtemp) then qmaps=qmaps..qtemp..',' -- see if there is a geocode boundary Qvalue to add else debugmsg(mw.addWarning('unable to add geocode value for '..mw.wikibase.getLabel..','..qtemp)) end end ct=ct+1 end end if qmaps and qmaps ~= '' then -- add back the created/expanded list of boundary Qvalues, and remove trailing comma args['map-data-light'] = string.sub(qmaps,1,#qmaps-1) end end -- Create the basemap using mapframe local mapframecontent=getmapframecontent(args,'basemap') table.insert(result, frame:extensionTag{ name ='mapframe', content=mapframecontent, args={width=tostring(maplist.width), height=tostring(maplist.height), zoom=tostring(maplist.zoom), longitude=tostring(maplist.lonbase), latitude=tostring(maplist.latbase), mapstyle=maplist.mapstyle, frameless=true } } ) --Add coverall box to block the unhelpful links from mapframe - which wouldn't include all the dots. Reinstate some links to osm and wikimedia table.insert(result,'<div style="position: absolute;width:'..tostring(maplist.width)..'px; height:'..tostring(maplist.height)..'px;') table.insert(result,'top:0px;left:0px;background-color:#FFFFFF00; color:inherit;"></div>') --Add replacent hover-links for OpenStreetMap and maps terms and conditions table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-13)..'px; width: 12px; height: 12px">') table.insert(result,'[[file:Transparent.svg|12px|link=https://www.openstreetmap.org/copyright|'..aboutOSM..']]</div>') table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-110)..'px;') table.insert(result,'width: 12px; height: 12px background-color: transparent; color:inherit;">') table.insert(result,'[[file:Transparent.svg|12px| link=https://foundation.wikimedia.org/wiki/Policy:Maps_Terms_of_Use|'..termsOfUse..']]</div>') -- Add scale-line if not args.scalemark or args.scalemark~='0' then local top=maplist.height-42 local left=maplist.width-61-(tonumber(args.scalemark or '1')) local minipos=string.lower(args.minimap or '') -- scalemark gets pushed left if it would be behind the minimap if minipos~='' and not(string.find(minipos,'left') or string.find(minipos,'top') ) then local offset=tonumber(args.scalemark or '1')-tonumber(args['mini-width'] or '60') if offset<1 then left=maplist.width-61-tonumber(args['mini-width'] or '60') end end if maplist.width-left >216 then top=top+14 end -- shunt scaleline down if it is beyond the copyright stuff local scalek,scalem=getScale(maplist.zoom, maplist.latbase, magVal) local magReduce='' table.insert(result,"<div style=\"display:inline-block; position: absolute; background-color: #111111") table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;M0,8 l0,4 l20,0 l0,-4 l-0.3,0 l0,3.7 l-19.4,0 l0,-3.7 z&#39;) ") table.insert(result,"; width:20px") -- path is a 20px20px box, and then rescales if magVal==1 then table.insert(result,"; top:"..tostring(top-1).."px") table.insert(result,"; left:"..tostring(left+16).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5)..", "..tostring(1.5)..")") else -- shrink the scalemark to compensate for magnification table.insert(result,"; top:"..tostring(top-(magVal*0.28)).."px") table.insert(result,"; left:"..tostring(left+(16*(magVal*1.15))).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5*(1/magVal))..", "..tostring(1.5*(1/magVal))..")") magReduce= 'scale('..tostring(1/magVal)..')' end table.insert(result,"\"></div>") table.insert(result,'<div style="position: absolute; top: '..tostring(top)..'px; left: '..tostring(left+47)..'px; font-size: 9.5px; line-height: 126%; width: fit-content;') table.insert(result,'color: #444433; background-color: transparent; text-align: right; transform: '..magReduce..' translateX(-100%);">'..scalek..'<br>'..scalem..'</div>') end --Set up the shapeList and dotList tables, to provide data to go on the map local sgNumbers,sgSortable={},{} --s1,s2 sgNumbers["1"]="1" if args.useFormatStyle and args.useFormatStyle=='shortstyle' then shapeList=ParseShapeTypes (shapeList,args,"1") for argindex,argv in pairs(args) do -- build a list of all the numbered sg's that have been used if string.find(argindex,"sg[a-f,n%d]+") == 1 then -- only look through the sga,sgb, sgc,sgd,sge,sgf and sgn args local x=string.match(argindex,"[%d]+") -- find its number if x and not sgNumbers[x] then sgNumbers[x]=x end -- only add if not already found end if string.find(argindex,"sg[a-f]H") == 1 and highlightNum then highlightOption=true end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form for k,v in pairs(sgSortable) do -- work through the sorted list, parsing each set of sg's in turn, from 1 upwards shapeList=ParseShapeTypes (shapeList,args,v) end if highlightOption==true then shapeList=ParseShapeTypes (shapeList,args,'H') end end local dotList,dotresult,dotItemTable,dotGroupList={},{},{},{} if (not args.useFormatStyle) or args.useFormatStyle=='standardstyle' then local dotmax=0 local qset={} for indx,val in pairs(args) do if string.match(indx,'mark%-coord[%d]+') or string.match(indx,'lat[%d]+') or string.match(indx,'geo%-data[%d]+') then dotmax=math.max(dotmax, tonumber(string.match(indx,"[%d]+"))) if string.match(indx,'geo%-data[%d]+') then local argnum=string.sub(indx,9,#indx) or '' if args['mark-title'..argnum] == 'none' or (string.find(args['mark-title'..argnum] or 'x','nolist')== 1) then -- debugmsg('sort values ignored '..argnum..', '..args['mark-title'..argnum]) else qset[val]=getQset (val, (args['geo-data-type'] or 'P1082'), argnum, args) end end end if (indx=='shapeH') or (indx=='shape-colorH') or (indx=='shape-outlineH') or (indx=='label-colorH') then highlightOption=true end end local qlist=args['geo-data-list'] if qlist then if string.sub(qlist,#qlist) ~= ',' then qlist=qlist..',' end else qlist='' end local geoRegion = args['geo-region'] -- or add base-page here local geoSubRegion = args['geo-sub-region'] or 'P150' if not mw.wikibase.isValidEntityId(geoSubRegion) then geoSubRegion = 'P150' end if (args['find-geo-dots'] or '1') ~= '0' and geoRegion and mw.wikibase.isValidEntityId(geoRegion) then local ct=1 local qtemp while (mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct]) do -- debugmsg('table '..ct..' - '..mw.wikibase.getBestStatements(adminRegion,adminSubRegion)[ct].mainsnak.datavalue.value.id) qtemp = mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id or nil if qtemp then qlist=qlist..qtemp..',' -- add subregion Qvalue to the list end ct=ct+1 end end if qlist and #qlist>1 then -- create a table from the csv list local _, c = qlist:gsub(",","") local qtab=splitItem(qlist,c) for xd,yd in pairs(qtab) do yd=string.upper(yd) if qset[yd] == nil and mw.wikibase.isValidEntityId(yd) then dotmax=dotmax+1 qset[yd]=getQset (yd, (args['geo-data-type'] or 'P1082'), tostring(dotmax), args) args['geo-data'..tostring(dotmax)] = yd -- debugmsg(dotmax..' = '..(string.upper(yd) or 'Q Not found')..': '..(mw.wikibase.getLabel(yd) or 'label Not found')..': '..(mw.wikibase.getSitelink(yd) or 'link Not found')) elseif not mw.wikibase.isValidEntityId(yd) then debugmsg(mw.addWarning(yd..' is not a [[wikidata]] page. geo-data-list needs comma-separated Qvalues' )) else debugmsg(mw.addWarning(yd..' is duplicate')) end end end -- qset contains set of Qvalues and can be from wikidata geo-region, list of geo-data-list Q values or individual geo-data1= etc local ts = {} -- table to hold the keys local useQset=false for ky, val in pairs(qset) do table.insert(ts, {key = ky, value = val[1], num=val[2]}) useQset=true end if useQset then local order = string.lower(args['geo-list-sort'] or 'a') -- sort the list from previously stored sortkey, either Lowest or Highest first if string.find(',al,vl,nl,wl,west,w,alpha,a,' , ','..order..',') then table.sort(ts, function (a,b) return lessthan(a.value, b.value) end) else table.sort(ts, function (a,b) return morethan(a.value, b.value) end) end for ky,val in ipairs(ts) do -- assign values from sorted list to numbered parameter args['numbered'..val.num] = ky -- The numbered parameter becomes the value for each dot that has a geo Qvalue, from manual, list or wiki sources. end end dotItemTable=tradstyleParseShapes(args,dotItemTable,dotmax) for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) or args['geo-data'..x] then sgNumbers[x]=x -- add it to the list end end else for indx in pairs(args) do if string.match(indx,'dot[%d]+') then table.insert(dotList,indx) end --add the index name for dot1, dot2 etc to dotList end table.sort(dotList,morethan) for indx,dotName in pairs(dotList) do dotresult=ParseData(args,string.match(dotName,'[%d]+') ) -- using each dot number, assign the settings for each dot to a dotresult item line table.insert(dotItemTable,dotresult) -- and store that item line within the dotItemTable end end for arcVal = 65,91 do -- check through args looking for any arcs local arcLetter=string.char(arcVal) if args['arc-text'..arcLetter] or args['arc'..arcLetter] then makeArcText(args,result,arcLetter) end end local dotdivs='' local ddots=0 if dotItemTable[1] then local ddots=(dotItemTable[1].lat or 0)+(dotItemTable[1].lon or 0) end local fgroup='F'..tostring(maplist.latbase+maplist.lonbase+ddots ) local FullscreenList={} local addcomma='' for i,dotitem in pairs(dotItemTable) do -- working throug each dot item, merge the dot and shape values into a full set of css text local dotgroup= dotitem.group or "0" if dotitem.posType=='mark-line' and dotitem.markDest then --find destination xy values for any mark-lines for n,v in pairs(dotItemTable) do if v.code == dotitem.markDest then dotitem.gridx2=v.gridx dotitem.gridy2=v.gridy break end end end local qtype=dotitem.group -- find which shape group each dot has been assigned --debugmsg('dotgroup='..qtype..', sg='..(sgNumbers[qtype] or 'nil')..' , shapeList='..shapeList[qtype].shape) if not sgNumbers[qtype] then qtype="0" end --shapeList[dotitem.group] will give access to the shape values for that dot if highlightNum==dotitem.code and highlightOption==true and shapeList['H'] then table.insert(result, getshapetable(dotitem,shapeList['H'])) else table.insert(result, getshapetable(dotitem,shapeList[qtype])) -- Add the actual css instructions for each dot end if shapeList[dotgroup] and not string.find((dotitem.param1 or ''),'nomap') then -- only add if not excluded with 'nomap' labelText local ftext='' if dotitem.dotTag~='' and not string.match(dotitem.labelText or '','[%d]') then ftext=stripdivs(dotitem.dotTag or '')..' <br>' end if (dotitem.labelText ~= ftext) and dotitem.dotLink =='' then ftext=ftext..' '..stripdivs(dotitem.labelText)..'<br>' end if (dotitem.dotLink) and (dotitem.dotLink ~='') then ftext=ftext..dotitem.dotLink..'<br>' end if dotitem.imageName then ftext=ftext..'[[File:'..dotitem.imageName..'|250px]]' end table.insert(FullscreenList,1, makeFullscreenItem (string.gsub(ftext,"[\n]+"," "), dotitem.info,round(dotitem.lat,5),round(dotitem.lon,5),fgroup,shapeList[dotgroup].shapeColor)..addcomma ) addcomma=', ' end -- makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) -- Always add to start of list, to reverse the sequence, and separate with commas except for first item, which is now at the end end if args.legendBox then makeLegendBox(result,args) end if args.minimap or args['mini-locator'] then makeLocatorMap(args,result) end -- add tag link and details for fullscreen version addcomma='' if (mapframecontent or '[]') ~= '[]' then addcomma=',' end mapframecontent=getmapframecontent(args,'fullscreen') local contentstart='[ '..mapframecontent..addcomma..'{ "type": "FeatureCollection", "features": [ ' --extra features after first square bracket local contentend=' ] } ]' table.insert(result, '<div style="position: absolute;top: 9px;left: '..tostring(maplist.width-34)..'px">') table.insert(result, '<div style="color: white; opacity:100; font-size: 19px; font-weight:normal; text-align: left;">') table.insert(result, frame:extensionTag{ name ='maplink', content=contentstart..table.concat(FullscreenList)..contentend, args={zoom=tostring(maplist.zoom+1), class='no-icon', frameless='1', latitude=tostring(maplist.latbase), longitude=tostring(maplist.lonbase), --add invisble 'en-spaces' for tooltip text='<div title="'..fullscreenlinktext..'">&nbsp;&nbsp;&nbsp;</div>'} } ) table.insert(result,'</div></div>') -- end of maplink ----- -- add closing div for main map table.insert(result,'</div>') -- collate caption material to go in the outer div class if captionTable ~= '' and (not args['auto-caption']) then args['auto-caption']='on' end --default to include list if using geo values local autocaption=string.lower(args['auto-caption'] or 'no') local autoOff=autocaption:match("(%w+)(.*)") -- select the first word in autocaption and see if it is a negativeAnswer) if negativeAnswer[autoOff] then captionTable='' end if args.caption or (not negativeAnswer[autoOff]) then table.insert(result,'<div class="thumbcaption" style="text-align:left">') if args.caption then table.insert(result,args.caption) if captionTable ~= '' then table.insert(result,'<hr>') end end end local columns=tonumber(autoOff:match("[%d]+") or '1') if columns>1 then columns=round(maplist.width/(columns*17), 0) end -- convert from em to px for historical reasons --for k in pairs(dotList) do capchk=capchk..(args["dotlink"..k] or '') end local capchk=nil local captionList = {} for key, value in pairs(dotItemTable) do -- only add an autocaption line if there is both a dotTag and a dotLink line available and it is not marked as nolist if value.dotTag and value.dotTag~='' and (not string.find(value.param1 or '','nolist')) and string.gsub(value.dotLink or '',"%s+","")~='' then table.insert(captionList, {key = key, value = value}) if captionTable == '' then capchk=true else -- add the next line of content to the html table capchk=false end end end if captionTable ~= '' then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) for k,v in pairs(captionList) do local nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag local ngrp=v.value.group if captionTableText[ngrp] then -- debugmsg('table text for'..v.value.dotTag..', '..captionTableText[v.value.group][1]..', '..captionTableText[v.value.group][2]..', '..captionTableText[v.value.group][3]..' ('..captionTableText[v.value.group][4]..')') local c1,c2 c1,c2=checkColors(shapeList[ngrp].shapeColor) local cDot='<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;' cDot=cDot..'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div>' captionTable = htmlTableLine(captionTable,cDot,captionTableText[ngrp][1], captionTableText[ngrp][2], captionTableText[ngrp][3], captionTableText[ngrp][4]) end end end if capchk and (not negativeAnswer[autoOff]) then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) -- local myDivision = string.gsub((args.toggletext or toggletext), "%s+", "") if string.find(autocaption,'collaps') then table.insert(result,'<div class = "wikitable mw-collapsible ') if string.find(autocaption,'collapsed') then table.insert(result,'mw-collapsed') end table.insert(result,'" style="width:100%; line-height: 17px; border-style:none; text-align: center"><b>'..(args.toggletext or toggletext)..'</b>') end if string.find(autocaption, 'columns=') then columns=string.match(autocaption,'[%d]+',string.find(autocaption, 'columns=') ) end table.insert(result,'<div class="mw-collapsible-content" style="column-count:'..columns..'; column-rule:solid 1px;text-align:left;padding-top:5px">') table.sort(dotList,lessthan) local nval,ngrp='','0' for k,v in pairs(captionList) do nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag ngrp=v.value.group or '0' if v.value.dotLink and v.value.dotLink~='' and nval and nval~='' then local c1,c2 if nval==args.highlight then c1,c2=checkColors(shapeList['H'].shapeColor) else c1,c2=checkColors(shapeList[ngrp].shapeColor) end table.insert(result,'<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;') table.insert(result,'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div> '..v.value.dotLink..'<br>') end end table.insert(result,'</div>') -- end for caption-content div if string.find(autocaption,'collaps') then table.insert(result,'</div>') end -- end for toggle frame end if captionTable ~= '' then table.insert(result,captionTable..'</table>') end if args.caption or (not negativeAnswer[autoOff]) then table.insert(result,'</div>') end -- end for whole caption frame table.insert(result,'</div></div>') -- outer two frames if args.float == 'center' or args.float=='centre' then table.insert(result,'</div>') end if args['show-new-format'] == 'hints' then -- provide a 'format hint panel' in the 'Preview Box' local w="<small>Below are some template hints for the 'sga' compressed version, " w=w..'{{tl|OSM Location dots}}. It can use these, instead of the more verbose {{tl|OSM Location map}} parameter format. ' w=w..'Data is divided between a "ShapeGroup" and the "Dots", so that a single shapeGroup can be used for multiple dots on the map. ' previewMsg(w..'(nb. the "group" value can be the number or an assigned name of a shapeGroup)</small>') w='<small>{{tl|OSM Location dots}}: | dot(n)=group,lat,lon,dotTag | ' w=w..'dotlink=link/tooltip | dotlabel=label,position,dx,dy,param1,info | dotpic=filename <br>' w=w.."(nb. param1 options include 'nolink' 'nolist' 'nomap' 'hemisphere-1' 'hemisphere+1', 'noline' - quotes not required, separate with spaces).<br>" w=w..'| sga = Shape,Sizepx,Color,Angledeg | sgb= OutlineWidth,Color,Style | sgc=TextSize,Color,Angle,bold italic <br>' w=w..'| sgd=TagSizepx,Color,Spacer,Angledeg | sge=LineWidth,Color,Style | sgf=TextSpacingpx,LineHeight%,Outlinepx,backgroundColor<br>' w=w..'| sgn=Name (optional, to assign a meaningful name) | sgp=Parent (can be the name or number of the parent shapeGroup. ' previewMsg(w..'Each shapegroup will inherit values from a parent, stretching back to "sga1" and its default values.)</small>') if #pmsg then local dbg={} for i,x in pairs(pmsg) do table.insert(dbg,x..'<br>') end dbg=mw.addWarning(table.concat(dbg)) table.insert(result, dbg) end end if args.coordtest then debugmsg(mw.text.nowiki(args.coordtest)) end for i,x in pairs(msg) do table.insert(result, x..'<br>') end return table.concat(result) end function p.main(frame) local args = getArgs(frame) local itemTab={} maplist.width=tonumber(args.width) or 400 maplist.height=tonumber(args.height) or 300 if args.coord then itemTab=splitItem(convertCoordsTrad (args.coord),2) maplist.latbase=itemTab[1] maplist.lonbase=itemTab[2] else maplist.lonbase=tonumber(args.lon) or 5 maplist.latbase=tonumber(args.lat) or 0 end maplist.zoom=tonumber(args.zoom) or 1 visibleLinks=args.showlinks highlightNum=args.highlight if args.nolabels=='1' then maplist.mapstyle='osm' else maplist.mapstyle='osm-intl' end if maplist.width >= 400 and not(args['show-q-values']) then args['show-q-values'] = '1' -- default to on if wider than 400px elseif maplist.width < 320 then args['show-q-values'] = '0' end -- turn off Qvalus if frame is too narrow, even if requested return p._main(args) end return p dtk9dp67voi5ll81vk4xxue11pgzo4j 204450 204449 2026-07-05T14:37:27Z HaxelKaszëba 17492 204450 Scribunto text/plain require('strict') local delink=require('Module:Delink').delink local getArgs = require('Module:Arguments').getArgs local p = {} local maplist={} local sgNames={} local highlightOption=false local highlightNum local visibleLinks local geoTotalPop = 0 local captionTable = '' local captionTableText = {} -- This module creates framed maps of anywhere in the world, at the required scale, and enables annotations, -- dots, shapes, lines and other ways to customise the area of the map being shown. It also provides a link -- to an interactive fullscreen version, which has locator dots instead of annotations and shapes. -- This is the 2025 successor module to a wiki-markup template version of 2024, which itself was a successor -- to the 'Graph'/VEGA driven template that was begun in 2016, until the Vega version was switched off in 2023. -- This module is called from template {{OSM Location map}}, which uses the same parameter formats as before. -- In addition it may at some point be possible to use a more concise parameter format using the template {{OSM Location dots}} -- In general the css output from the two formats will be identical, but the the concise version will allow bits of -- greater control over some of the settings. -- see the documentation on the two template pages for details of how to use the mapping features. -- If language customisation is needed, there are text items below that can be translated. Also see the color table -- below with details of how to add additional color names to allow localised alternatives. -- (Translating other language shape-types could be possible, but has not currently been contemplated. -- Parameter name translation would be harder but likely to be possible, ideally still retaining compatibility -- with template calls already written using English). -- -------------- the following text variables are used within the template, and can be translated as required --------------- -- -------------- you may wish to retain the English version as a comment, to keep track of the original phrase -------------- local negativeAnswer={no=1,'0'-1,off=1} local fullscreenlinktext = 'Click for interactive fullscreen map with links to nearby articles' local toggletext = 'List of numbered map items:' local termsOfUse = 'Maps: terms of use' local aboutOSM = 'About OpenStreetMaps' local yards = 'yds' local miles = 'miles' local metres = 'm' local km = 'km' local sqkm = 'sq.km' local year = 'year' local notFound = 'not found' local totalItems = 'Total' local sumOfDatapoints = 'sum of datapoints' local numberCol = 'No.' -- abbreviated for top of column of code numbers local wikidataLink = 'Wikidata page' local shapeList={} --This sets up the 'factoryDefault' shape group 0 shapeList["0"]={shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border of the shape outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items text settings for labels textSZ="11px", textCL="darkgrey", textNG="0deg", --sgf further text settings textSP="0px", textLH="120%", textOL="0px", textBG="transparent", --sgd items for dotTag text settings tagSize="10px", tagColor="white", tagSpacer="0px", tagAngle="0deg", --sge items for extension line to connect label to dot textEW="0px", textEC="darkgrey", textES="solid" } local colorList={} -- used by colorLookup to catch unsupported colors (eg 'LimeGreen'), to convert to generic version colorList['green']='hardgreen' -- it could also be added to to include alternative language equivelants, for a quick solution. colorList['red']='hardred' -- colorList ['source'] = target colorList['white']='white' -- converts any color that includes 'source' into its equivelent target colorList['blue']='hardblue' -- note, for translation you can add to this list, rather than replace it, colorList['brown']='brown' -- which would mean existing map definitions in english would also still work, alongside translated ones colorList['grey']='hardgrey' colorList['gray']='hardgrey' colorList['purple']='hardpurple' colorList['orange']='hardorange' colorList['leaf']='hardleaf' --for a more thorough translation, you can add all the variants of the colors as further CTB elements and hex values or redirects local CTB={} -- set up a table of color names (the CTB Color table index) and html hash colorhex values. CTB["paleblue"],CTB["softblue"],CTB["hardblue"],CTB["darkblue"]="#D6E1EC","#77A1CB","#4B77D6","#1c559e" CTB["palered"],CTB["softred"],CTB["hardred"],CTB["darkred"] = "#FCC6C0","#EC644B","#DB3123","#AA1205" CTB["palegreen"],CTB["softgreen"],CTB["hardgreen"],CTB["darkgreen"]= "#D2F0E5","#81AF81","#269F46","#0b7527" CTB["paleleaf"],CTB["softleaf"],CTB["hardleaf"],CTB["darkleaf"]= "#dff5c1","#b5e376","#8cc244","#679c21" CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"]= "#E8E8D6","#AAAA88","#777755","#444433" CTB["palegray"],CTB["softgray"],CTB["hardgray"],CTB["darkgray"]=CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"] CTB["palebrown"],CTB["softbrown"],CTB["hardbrown"],CTB["darkbrown"]="#FAF6ED","#CCB56C","#AD7F14","#754910" CTB["palepurple"],CTB["softpurple"],CTB["hardpurple"],CTB["darkpurple"]="#e0d1e6","#c784e0","#a029cf","#7a05a8" CTB["paleorange"],CTB["softorange"],CTB["hardorange"],CTB["darkorange"]="#ffedc2","#ffcf61","#EEB533","#e39f05" CTB["black"],CTB["white"],CTB["yellow"]="#000000","#FFFFFF","#FAF039" CTB["background"],CTB["paleground"],CTB["beigeground"]="#f9f6f2","#FEFEFA","#F5F5DC" CTB["beige"]=CTB["beigeground"] CTB["aqua"],CTB["teal"],CTB["fuchsia"] = "#00FFFF","#008080","#FF00FF" CTB["maroon"],CTB["olive"],CTB["navy"] = "#800000","#808000","#000080" CTB["lime"],CTB["limegreen"],CTB["aquamarine"] = "#00FF00","#32CD32","#7FFFD4" CTB["silver"],CTB["yellow"],CTB["orchid"] = "#800000","#FFFF00","#DA70D6" -- set up a table of predefined clip-paths local pathshape={} pathshape.squaredd = "M 19,1.25 l 0,18 -18,0 0,-18 18,0m-1,1 -16,0 0,16 16,0 0,-16m-1,1 0,14 -14,0 0,-14 14,0zm-1,1 -12,0 0,12 12,0 0,-12zm-1,1 0,10 -10,0 0,-10 10,0z" pathshape.squared = "M 18,2.5 l 0,15 -15,0 0,-15 15,0m-1,1 -13,0 0,13 13,0 0,-13zm-1,1 0,11 -11,0 0,-11 11,0z" pathshape.triangledd="M 0 20,20 20,10 0,0 20ZM1.5 19,10 1.7,18.5 19,1.5 19ZM3 18,17 18,10 3.8,3 18ZM4.5 17,10 5.4,15.4 17, 4.5,17ZM6 16,13.8 16,10 7.4z" pathshape.triangled ="M1,18 l 18,0 l -9,-18 l -9,18zm1.7,-1.1 l 7.3,-14.6 l 7.3,14.6 l -14.6, 0zm1.7,-1 l 11.0,0 l -5.5,-11 l -5.5,11z" pathshape.circledd = "M0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm0.8,0a9.2,9.2 0 1,1 18.4,0a9.2,9.2 0 1,1 -18.4,0m1,0a8.2,8.2 0 1,0 16.4,0a8.2,8.2 0 1,0 -16.4,0zm0.8,0a7.2,7.2 0 1,1 14.8,0a7.2,7.2 0 1,1 -14.8,0m1,0 a6.4,6.4 0 1,1 12.8,0a6.4,6.4 0 1,1 -12.8,0z" pathshape.circled = "M2.5,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0m0.8,0 a5,5 0 1,1 11.4,0a5,5 0 1,1 -11.4,0" pathshape.diamond = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10z" pathshape.diamondd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm1,0 l 6,8.5 l 6,-8.5 -6,-8.5 -6,8.5zm1,0 l 5,-7 5,7 -5,7 -5,-7z" pathshape.diamonddd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm0.75,0 l 6.25,9 l 6.25,-9 -6.25,-9 -6.25,9zm0.75,0 l 5.5,-8 5.5,8 -5.5,8 -5.5,-8zm0.75,0 l 4.75,7 l 4.75,-7 -4.75,-7 -4.75,7zm0.75,0 l 4,-6 4,6 -4,6 -4,-6z" pathshape.crossd = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0zM2.3,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.cross = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0z" pathshape.thincross = "M2,12 l6,0 l0,6 l4,0 l0,-6 l6,0 l0,-4 l-6,0 l0,-6 l-4,0 l0,6 l-6,0z" pathshape.fivepointstar = "M10 0 L12.245 6.91 19.511 6.91 13.633 11.18 15.878 18.09 10 13.82 4.122 18.09 6.367 11.18 0.489 6.91 7.755 6.91Z" pathshape.fivepointstard = "M10 1.5 L 11.90825 7.3735 18.08435 7.3735 13.08805 11.003 14.9963 16.8765 10 13.247 5.0037 16.8765 6.91195 11.003 1.91565 7.3735 8.09175 7.3735 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sixpointstar = "M10 0 L12.323 5.977 18.66 5 14.645 10 18.66 15 12.323 14.023 10 20 7.677 14.023 1.34 15 5.355 10 1.34 5 7.677 5.977Z" pathshape.sixpointstard = "M10 1.5 L 11.97455 6.58045 17.361 5.75 13.94825 10 17.361 14.25 11.97455 13.41955 10 18.5 8.02545 13.41955 2.639 14.25 6.05175 10 2.639 5.75 8.02545 6.58045 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sevenpointstar = "M10 0 L12.048 5.747 17.818 3.765 14.602 8.95 19.749 12.225 13.69 12.943 14.339 19.01 10 14.72 5.661 19.01 6.31 12.943 0.251 12.225 5.398 8.95 2.182 3.765 7.952 5.747Z" pathshape.sevenpointstard = "M10 1.5 L11.7408 6.38495 16.6453 4.70025 13.9117 9.1075 18.28665 11.89125 13.1365 12.50155 13.68815 17.6585 10 14.012 6.31185 17.6585 6.8635 12.50155 1.71335 11.89125 6.0883 9.1075 3.3547 4.70025 8.2592 6.38495 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.eightpointstar = "M10 0 L11.88 5.46 17.071 2.929 14.54 8.12 20 10 14.54 11.88 17.071 17.071 11.88 14.54 10 20 8.12 14.54 2.929 17.071 5.46 11.88 0 10 5.46 8.12 2.929 2.929 8.12 5.46Z" pathshape.eightpointstard = "M10 0 L10 1.5 L11.598 6.141 16.01035 3.98965 13.859 8.402 18.5 10 13.859 11.598 16.01035 16.01035 11.598 13.859 10 18.5 8.402 13.859 3.98965 16.01035 6.141 11.598 1.5 10 6.141 8.402 3.98965 3.98965 8.402 6.141ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.ring="M2.6,9.5a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.semicircle = "M0,5 C0,18.3,20,18.3,20,5 L 0,5z" pathshape.arrowhead = "M 10,10.2 l 10,2.8 -2,-3 2,-3 -10,2.8 -0.2,0.2z" pathshape.boxd=pathshape.squared pathshape.boxdd=pathshape.squaredd pathshape.ellipsed=pathshape.circled pathshape.ellipsedd=pathshape.circledd local msg={} local function debugmsg(txt) table.insert(msg,txt) end local pmsg={} local function previewMsg(txt) table.insert(pmsg,txt) end local function colorLookup(color) for c,l in pairs(colorList) do if string.find(color,c) then return l end end return color end local function getColor (color,chk) local c local opacity="100" if not color or color=='' then color='hardgrey' end if color=="transparent" then return color end if color=="background1" then color='background' end if string.byte(color,1,1)==35 and (#color == 7 or #color == 9) then c=color elseif string.byte(color,1,1)==35 and #color == 4 then c=string.sub(color,1,2)..'f'..string.sub(color,3,3)..'f'..string.sub(color,4,4)..'f' else local s=color..'1' s= s:sub(0,s:find("%d")-1) opacity=string.match(color,"%d+") if not CTB[s] then s= colorList[s] end -- check for synonyms and translations if not CTB[s] then debugmsg(mw.addWarning('color = '..color..'. The color name is not defined. Used default grey instead')) end c=CTB[s] or CTB.hardgrey end if opacity and (tonumber(opacity) < 100) and string.find(c,"#")==1 and string.len(c)==7 and opacity~="" then local hexval=string.format("%x",(math.floor(tonumber(opacity)*2.55))) c=c..hexval end return c end function p.colorvalue(frame) -- enable external access to the CTB colorTable values. usage: {{#invoke:OSM Location map|colorvalue|color=hard blue}} local c if not frame.args.color or frame.args.color=='' then c='grey' else c=string.lower(string.gsub(frame.args.color,'%s+','')) end return string.upper(string.sub(getColor(c),2)) end local function checkColors(color) local c=getColor(color,'check') local opacity =1 -- calculate colour brightness and return black or white for contrast if c=='transparent' then return c,'#000000',0 end if not (string.find(c,'#')==1) then return c,'#FFFFFF',0 end if #c>8 then opacity= tonumber('0x'..(string.sub(c,8,9)))/500 end local r=tonumber('0x'..(string.sub(c,2,3)))/255 local g=tonumber('0x'..(string.sub(c,4,5)))/255 local b=tonumber('0x'..(string.sub(c,6,7)))/255 if 0.2126 * r + 0.7152 * g + 0.0722 * b / opacity < 0.7 then return c,'#FFFFFF',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity else return c,'#000000',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity end end local function morethan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) and tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%-?%f[%d]%d[,.%d]*%f[%D]') ) b = tonumber(string.match(b, '%-?%f[%d]%d[,.%d]*%f[%D]') or '0') end return a>b end local function lessthan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) and tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%-?%f[%d]%d[,.%d]*%f[%D]')) b = tonumber(string.match(b, '%-?%f[%d]%d[,.%d]*%f[%D]') or '0') end return a<b end local function getsize(size) --size1 is between 1 and 3 values, each with px, equating to width,height,corner-rounding --eg '15px 25px 5px' (spaces are optional) or '18px'. returns three numbers local sizeval = {} for v in string.gmatch(size, "[^px]+") do table.insert(sizeval,v) end sizeval[1] = tonumber(sizeval[1]) or 13 sizeval[2] = tonumber(sizeval[2]) or sizeval[1] sizeval[3] = tonumber(sizeval[3]) or 0 return sizeval[1],sizeval[2],sizeval[3] end local function coord2text(coord) -- looks through the output from {{coord}} to find the lat and long decimal values -- and converts compass points to minus or not-minus, return with separating comma. local lat = string.match(coord,'[%.%d]+°[NS]') local lon = string.match(coord,'[%.%d]+°[EW]') local neg={N="",S="-",W="-",E=""} return neg[string.match(lat, '[NS]')]..string.match(lat,'[%.%d]+')..","..neg[string.match(lon, '[EW]')]..string.match(lon,'[%.%d]+') end local function convertCoordsTrad (row) local coords='' if row and string.find (row,'<span class="geo">') then local a,b=string.find(row,'<span class="geo">') local start=b+1 a,b=string.find(row,"</span>",b) local finish=a-1 coords=string.sub(row,start,finish) coords=string.gsub(coords,'; ',',') end return coords end local function convertCoords (row) local start,finish,lat,lon,coords,says if row then local a,b=string.find(row,"<span class=") start=a while a do -- find the final span> finish=b a,b=string.find(row,"span>",b) end if start then coords= string.sub(row,start,finish) says="" if string.find(coords,'<span class="error">') then error("coord error: badly formed coordinates",0) end coords=coord2text(coords) coords = string.sub(row,1,start-1)..coords..string.sub(row,finish+1) else coords=row end return coords else return "Nothing to see here" end end local function fillCommas(val,max) local line='' if not val then line=',' -- ensure there is some content else line = val --string.lower(string.gsub(val,"%s+","")) -- or strip spaces end if string.find(line,',') == 1 then line=' '..line end -- ensure initial comma is not skipped local _, count=string.gsub(line,",","") -- add enough subsequent commas for all entries line=line..string.rep(',',max-count) while(string.find(line,",,") ) do line=string.gsub(line,",,",", ,") --ensure string.gmatch doesn't ignore any empty items by padding with spaces end return line end local function makeLinkBox(left,top,wid,label, link) local linkBoxName='Transparent square.svg' if visibleLinks or '' =='yes' then linkBoxName='Red hollow square.svg' end local builder = mw.html.create('div') --display:inline-block; local t= string.find(label,'wikidata') -- exclude wikidata from tooltip line if t then label=string.sub(label,1,t-2) -- don't show wikidata value in tooltip if string.find(link,'wikidata') then link = '' end -- don't link on click to wikidata (if there was not another link) end builder :cssText('position:absolute;left:'..tostring(left-1-wid/2)..'px;top:'..tostring(top-1 + math.min(wid/2-12,0) - wid/2)..'px') :wikitext(string.format( '[[File:%s|%dpx|link=%s|%s]]', linkBoxName, wid+2, link, label )) return tostring(builder) end local function extractItem(row,searchItem) -- remove text following a searchItem or start of line, which might be in quote-marks to allow commas local xend,xstart=1,0 if not row then return '','' end if searchItem then xend,xstart= string.find(row or '',searchItem or 'image:') end if not xstart then return string.gsub(string.gsub(row or '',"%b\"\"", ''),"%b\'\'", '') or '','' end while row:byte(xstart+1) == 32 and xstart<#row do -- skip over any leading spaces xstart=xstart+1 end local xbyte=row:byte(xstart+1) if xbyte == 34 or xbyte == 39 then -- are they wrapped in single or double quotes xstart=xstart+1 xend=row:find(string.char(xbyte),xstart+1) else xend = row:find(',',xstart+1) -- if no quotes, we assume no commas if not xend then xend=#row+1 end end -- return residual row and extracted text return row:sub(0,xstart)..row:sub(xend), row:sub(xstart+1,xend-1) end local function itemCheck(item,ext) if not item then return nil end if not ext then ext='' end return (string.match(item,"[%.%-?%d]+") or '0')..ext end local function stripdivs(line) return string.gsub(line or '',"%b<>", ' ') end local function splitItem(item,max) -- takes a commas-sep list and returns a table of lowercase items with no spaces, or nil local r={} local x=1 item=string.lower(fillCommas(item,max)) for t in string.gmatch(item,"[^,]+") do r[x]=string.gsub(t,"%s+","") if r[x]=='' then r[x]= nil else -- residual items might have commas if x>max then r[max]=(r[max] or '')..', '..r[x] end end x=x+1 end return r end local function ParseShapeTypes (result,args,sgval) -- for use with compressed, comma-separated 'sg plus dots' parameters --[[ shape table items and default values as set at top of page shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items label text textSZ="11px", textCL="white", textNG="0deg", textAT=attributes ("bold" and/or "italic") --sgd items for dotTag tagSize="11px", tagColor="darkgrey", tagSpacer="0px", tagAngle="0deg", --sge extension line textEW="1px", textEC="darkblue", textES="solid" --sgf fx for text labels textSP="0px" textLH="100%" textOL="1px", textBG="paleground", <!--| sga = shape,Size,Color,Angle|sgb= outlineWidth,color,style eg: sga1=circle,14px,blue,0deg| sgb1=0px,dark grey,solid | sgc=textSize,color,angle,bold/italic | sgd=tagSize,Color,Spacer,Angle eg: sgc1=11px,dark grey,0deg,normal| sgd1=9px,white,0px,0deg | sge=lineWidth,color,style |sgf=textspacing,lineHeight%,outlinepx,backgroundcolor, [bold,italic] eg: sge1=0px,black, solid| sgf1=0px,120%,0px,background | dot=shape-group/lat/lon/title/dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> --]] if args["sgn"..sgval] then local sgname=string.match(args["sgn"..sgval],"(%w+)(.*)") sgNames[sgname]=sgval end local parent= args["sgp"..sgval] if parent then parent=string.match(parent,"(%w+)(.*)") local pos= string.find(parent,"%d+") if pos == 1 then parent=string.match(parent,"%d+") else parent=sgNames[parent] or '1' end end if sgval~='H' then if not parent or tonumber(parent) > tonumber(sgval) then if sgval=="1" then parent="0" else parent="1" end end end local itemTab, line, filename result[sgval]={} result[sgval].shapeType=sgval line,filename=extractItem(args['sga'..sgval] or '','image:') if sgval=='1' and not args.sga1 then line='circle,12px,blue,0deg' end -- ensure there is a parent=1 sga result[sgval].shapeFile=filename or '' -- sga= Attributes for shape itemTab=splitItem(line,4) result[sgval].shape = itemTab[1] or result[parent].shape result[sgval].shapeSize=itemTab[2] or result[parent].shapeSize result[sgval].shapeColor=itemTab[3] or result[parent].shapeColor result[sgval].shapeAngle=itemCheck(itemTab[4],'deg') or result[parent].shapeAngle -- sgb= Border outline attributes for shape itemTab=splitItem(args['sgb'..sgval],3) result[sgval].outlineWidth=itemCheck(itemTab[1],'px') or result[parent].outlineWidth result[sgval].outlineColor=itemTab[2] or result[parent].outlineColor result[sgval].outlineStyle=itemTab[3] or result[parent].outlineStyle --sgc=character attributes for label itemTab=splitItem(args['sgc'..sgval],4) result[sgval].textSZ=itemCheck(itemTab[1],'px') or result[parent].textSZ -- size of text in px result[sgval].textCL=itemTab[2] or result[parent].textCL -- colour for text result[sgval].textNG=itemCheck(itemTab[3],'deg') or result[parent].textNG -- Angle for text result[sgval].textAT=itemTab[4] or result[parent].textAT -- attributes bold, and/or italic --sgd=dotTag attributes itemTab=splitItem(args['sgd'..sgval],4) result[sgval].tagSize=itemCheck(itemTab[1],'px') or result[parent].tagSize result[sgval].tagColor=itemTab[2] or result[parent].tagColor result[sgval].tagSpacer=itemCheck(itemTab[3],'px') or result[parent].tagSpacer result[sgval].tagAngle=itemCheck(itemTab[4],'deg') or result[parent].tagAngle --sge= extension line attributes itemTab=splitItem(args['sge'..sgval],4) result[sgval].textEW=itemCheck(itemTab[1],'px') or result[parent].textEW -- width result[sgval].textEC=itemTab[2] or result[parent].textEC -- colour result[sgval].textES=itemTab[3] or result[parent].textES -- style --sgf= fx for label text itemTab=splitItem(args['sgf'..sgval],4) result[sgval].textSP=itemCheck(itemTab[1],'px') or result[parent].textSP -- spacing value for letters result[sgval].textLH=itemCheck(itemTab[2],'%') or result[parent].textLH -- Angle for text result[sgval].textOL=itemCheck(itemTab[3],'px') or result[parent].textOL -- width of text-border line result[sgval].textBG=itemTab[4] or result[parent].textBG -- color for text background return result end local function round(x,dec) -- x=number [, dec=integer] returns numeric value with upto dec decimals (all but first trailing zeros get truncated) if (dec or 0)==0 then return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5) --this avoids .0 where dec=0 end dec =10^(dec) return x>=0 and math.floor(x*dec+0.5)/dec or math.ceil(x*dec-0.5)/dec end local function maptogrid(t,r,test) --[[ converts mercator projection longitude and latitude coordinates to x and y pixel coordinates, within a frame of given size, centre coordinates and zoom level. t is a table of named indices: {lon, lat, lonbase, latbase, width, height, zoom} output is two values, x and y, rounded to r decimal places--]] local x,y x=t.width/2 + ( ((math.rad(t.lon)*6378137) - (math.rad(t.lonbase)* 6378137) ) / (156543.03*math.cos(t.latbase/180)/(2^t.zoom) ) )*(1-(0.075*(math.abs(t.latbase)/90))) y=t.height/2 + ( ( (math.log(math.tan(math.rad(t.latbase)/2+math.pi/4))*6378137) - (math.log(math.tan(math.rad(t.lat)/2+math.pi/4))*6378137) ) / (156543.03*math.cos(t.latbase/180) / (2^t.zoom) ) )*(1-(0.075*(math.abs(t.latbase)/90))) return round(x,r),round(y,r) --source: python code at https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames and https://wiki.openstreetmap.org/wiki/Mercator --[[ width and height are the size, in pixels, of the map, which will be centerd around lonbase,latbase. Method: Convert lon and lonbase to meter-offsets from coord(0,0), and subtract lonbase from lon, zoom and latbase are used to scale the resulting meter-offset to pixels, and add it to width/2. Convert lat and lat-base to meter-offsets from coord(0,0), subtract lat from latbase, scale the resulting meter-offset to pixels, add it to height/2. A correction factor '*(1-(0.075*(math.abs(t.latbase)/90) ) )' compensates for an error that seems to creep in towards the edges of the map at higher latitudes. It was identified experimentally, and ensures a dot at the edge is in the same place as if that location is positioned at the centre. Original Python code for lat,lon to x,y where 0,0 is the centre of the map print('x=',width+(((math.radians(lon1) * 6378137)- (math.radians(lonbase) * 6378137))/ (156543.03*math.cos(latbase/180)/(2**zoom))),' y=',height+(( (math.log(math.tan(math.pi / 4 + math.radians(latbase) / 2)) * 6378137)- (math.log(math.tan(math.pi / 4 + math.radians(lat1) / 2)) * 6378137))/ (156543.03*math.cos(latbase/180)/(2**zoom))) ) --]] end local function getScale(zoom, lat,magVal) lat = tonumber(lat) if not lat then error( "zoom=" .. tostring(zoom) .. " lat=" .. tostring(lat) .. " mag=" .. tostring(magVal) ) end if magVal and magVal > 1 and magVal <= 2 then zoom = zoom + (magVal - 1) end local dist = (156543.03 * math.cos(math.rad(math.abs(lat))) / (2 ^ zoom)) / 17 local y if dist < 1 then y=(round(dist*10,1)) return tostring(y*100)..metres, tostring(round(y*109,0))..yards elseif dist <18 then y=(round(dist,0)) return tostring(y)..km, tostring(round(y*0.621371,1))..miles elseif dist <500 then y=round(dist/10) return tostring(y*10)..km, tostring(round(y*6.21371,0))..miles else y=round(dist/100) return tostring(y*100)..km, tostring(round(y*62.1371,0))..miles end end local function ParseData (args,dotval) -- for use with compressed, comma-separated 'sg plus dots' parameters -- takes a structured series of comma-separated items which get parsed as the following: -- dot(n)= (sgNumber or Name),{{coord}} or (lat,lon), (dotTag) -- dotlink(n) = single-parameter text to give wikilink and/or title used by tootlip, fullscreen dots and autocaption list -- dotlabel(n) = 'label text',pos(left,roght,top,bottom,centre,auto),(dx), (dy) pixel offsets, params, info -- dotpic(n) = single parameter wikimedia filename for an image to use in photopanel and/or fullscreen dots -- dotfeature(n)= 'mark-line' (,linewidth,style,gap) or 'photo-panel' (,image-dim,panel-width,panel-height), draws line to n-1 -- label is used if either a position and/or an x,y offset are not 0,0 ( if no label then dotTag will be put at at the x,y offset, or over the dot -- label text can be autoaligned if x,y puts it left or right of the dot, or centered if above/below) -- quote marks are not needed unless including commas within the label text -- param1 is optional items separated by spaces, and can include [nolabel nolist nomap hemisphere+1 hemisphere-1] -- info is free wikitext, to be used in the fullscreen dot box. (use dotpic to show a picture) --<!--| dotx=shape-group,[lat,lon or {{coord}} ], dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> local result={} local count=1 local row = convertCoords (args["dot"..dotval]) -- swap in any {{coord}} values so they are csv lat and lon row=fillCommas(row,4) result.code=dotval -- store the parameter name as the id code for item in string.gmatch(row,"[^,]+") do -- iterate through 'row', adding each csv item in turn, if present if count==1 then --see if it is a number or a name local pos= string.find(item,"%d+") if pos == 1 then result.group=string.lower(string.gsub(item,"%s+","")) else item=string.match(item,"(%w+)(.*)") -- ensure just a single word result.group=sgNames[item] end elseif count==2 then result.lat=tonumber(string.match(item,"[%.%-?%d]+")) or 0-- find the number, with no non-numeric stuff elseif count==3 then result.lon=tonumber(string.match(item,"[%.%-?%d]+")) or 0 elseif count==4 then result.dotTag=item:match( "^%s*(.-)%s*$" ) or "" -- dotTag allows for internal spaces, but no commas end count=count+1 end row, result.labelText= extractItem(args["dotlabel"..dotval]) result.labelText= string.gsub(result.labelText,"[%^]+","<br>") -- convert hats to line breaks local item=splitItem(row,6) result.labelPos=item[2] or 'center' result.dx=tonumber(string.match(item[3] or '0',"[%.%-?%d]+")) or 0 result.dy=tonumber(string.match(item[4] or '0',"[%.%-?%d]+")) or 0 result.param1=string.lower(item[5] or '') if string.find(result.param1,"hemisphere-1",1,true) then result.lon=result.lon-360 elseif string.find(result.param1,"hemisphere+1",1,true) then result.lon=result.lon+360 end local txt = '' if item[6] then -- ensure all info elements are included, including commas count=1 local max=6 for t1 in string.gmatch(fillCommas(row,max),"[^,]+") do if count>max then txt=txt..',' end if count>=max then txt=txt..t1 end count=count+1 end end result.info=txt result.imageName = args['dotpic'..dotval] -- Get first wikilinked item (if any) from the args.dotlink and set this plus the delinked text local testx=args["dotlink"..dotval] or '' result.dotLink=testx if testx ~= '' then testx=stripdivs(testx) result.title=delink({ testx }) local linkstart= string.find(testx,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then result.dlink=delink( { string.sub(testx,linkstart,string.find(testx,']]',1,true)+1),wikilinks='target' } ) else result.dlink='' end else result.dlink='' result.title='' end if args['dotfeature'..dotval] then local item=splitItem(args['dotfeature'..dotval],6) if (item[1] or '') =='photo-panel' then result.ppwidth= tonumber(string.match((item[3] or '110'),"%d+")) -- panel width result.ppheight= tonumber(string.match((item[4] or '48'),"%d+") ) --panel height result.photowidth=round(tonumber(string.match((item[2] or '1.3'),"[%.%-?%d]+")) * result.ppheight+1,0) -- dimension to set image size result.photoImage=result.imageName result.posType='photo-panel' elseif (item[1] or '') == 'mark-line' then local x=tonumber(dotval or '0') result.markDest=item[5] or tostring(x-1) result.mlWidth= tonumber(string.match((item[2]) or '',"%d+") or '1') result.mlStyle= item[3] or 'solid' result.mlGap=tonumber(string.match((item[4] or ''),"[%d]+") or '0') result.posType='mark-line' end --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end maplist.lon=result.lon maplist.lat=result.lat result.gridx, result.gridy = maptogrid(maplist,1) -- convert geo coords to grid xy - using values from maplist table return result end local function multiCheck (args, argName, argVal, defVal, alt) if not alt then alt='nonexistant' end if argVal=='H' and not args[argName..'H'] then argVal=highlightNum or '1' end if argVal=='' then return (args[argName] or args[alt] or (args[argName..'D']) or args[alt..'D'] or defVal) -- unnumbered args do not inherit from D or 1 else return (args[argName..argVal]) or (args[alt..argVal]) or (args[argName..'D']) or (args[alt..'D']) or (args[argName..'1']) or (args[alt..'1']) or defVal end end local function htmlTableLine (htmlTable,h1,h2,h3,h4,h5,type) if type == 'header' then if (h4 or '') ~= 'no-year' then h4='<th class="unsortable">'..h4 or ''..'</th>' else h4 = '' end if (h5 or '') ~= '' then h5='<th class="unsortable">'..h5..'</th>' else h5 = '' end return htmlTable..'<tr><th>'..(h1 or '')..'</th><th>'..(h2 or '')..'</th><th>'..(h3 or '')..'</th>'..h4..h5..'</tr>' else if (h4 or '') ~= 'no-year' then h4='<td>'..h4 or ''..'</td>' else h4 = '' end if (h5 or '') ~= '' then h5='<td>'..h5..'</td>' else h5 = '' end return htmlTable..'<tr><td>'..(h1 or '')..'</td><td>'..(h2 or '')..'</td><td>'..(h3 or '')..'</td>'..h4..h5..'</tr>' end end local function formatInt(number) if number == 0 then return notFound end local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)') int = int:reverse():gsub("(%d%d%d)", "%1,") return minus .. int:reverse():gsub("^,", "") .. fraction end local function convertToSqKm (value,punit) if string.find(punit, 'Q35852') then value = value / 100 -- convert to sq km if it's hectares elseif string.find(punit, 'Q232291') then value = value /(1/2.59) -- or this if it's square miles elseif string.find(punit, 'Q81292') then value = value / 247.1 -- or this if it's acres end return value end local function assignTradstyleShape(shapeResult,default,dotResult,args,nval) local item,itemTab local autoDotTag='' local shapeWidth,shapeHeight=0,0 local argval=nval -- to catch the unnumbered shape series local qvalue='' if argval=='0' then argval='' end if nval=='H' then shapeResult.H={} end -- Assign autovalues if a wikidata Qvalue has been supplied -- some preliminary documentation -- geo-data-type= default is P1082 (population) -- other options might include number of households (P1538) and per capita income (P10622) or land area (P2046) -- geo-data-list= this is a comma-separated list of Q values, for a manually generated list that doesn't require nymbered parameters -- geo-scalefactor = can enlarge or shrink all the dots in proportion, depending an what the map needs -- geo-region = needs to be a Qvalue of the larger administrative region that the settlements/sub-regions are in -- geo-sub-region needs to be a Pvalue, but will default to P150 (some pages use 'has parts' = P527) -- geo-data3= etc numbered Qvalue item, if used in conjunction with other shape/label parameters -- shapeD= sets default shape, which migh be n-circle to include numbers. -- geo-number-size= will give a fixed size for shape numbers. (defaults to 12 when using proportional dots) -- geo-boundaries= if set to 1 will add any Qvalues for the sub-regions to 'map-data-light'. -- show-geo-dots= to use proportional dots. Will be fixed size if 0. (set mark-sizeD=0 to have no dot at all, eg to show labels or boundaries) -- find-geo-dots=1 or 0 (needs to also include geo-region) -- label-posD = gets additional options: [off] gives no label, [on] will position lower centre, -- [on+] includes numeric values beside/below label, [on^] will always put it below. [on-] always puts values beside if args['geo-data'..argval] then -- and (args['mark-title'..argval] or '') ~= 'none' then local pvalue = args['geo-data-type'] or 'P1082' local plabel = mw.wikibase.getLabel(pvalue) if not args['geo-number-size'] then args['geo-number-size']=12 end if not args['label-posD'] then args['label-posD'] = 'on' end qvalue=args['geo-data'..argval] -- total poulation is a Qvalue wikidata page entry, or add up all the geo-data values or use a random 10000 local region = args['geo-region'] -- or mw.wikibase.getEntityIdForCurrentPage() local thisPop=0 local areatxt='' local yearVal='' if mw.wikibase.getBestStatements(qvalue,pvalue)[1] then thisPop=tonumber(mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.amount) or 0 if pvalue == 'P2046' then -- area, so convert from ha to sq km if needed (/100) thisPop = convertToSqKm(thisPop, mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.unit) end if mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers and mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers.P585 then yearVal = mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers.P585[1].datavalue.value.time if yearVal then yearVal = yearVal:sub(2,5) end -- wikidata reports an entire date and time, so extract the year end else debugmsg(mw.addWarning(mw.wikibase.getLabel(qvalue)..' ('..qvalue..') has no recorded '..plabel)) end local scalefactor=args['geo-scalefactor'] or 1 if argval=='1' and args['mark-size1'] and not args['mark-sizeD'] then args['mark-sizeD'] = args['mark-size1'] end if argval=='1' and args['label-pos1'] and not args['label-posD'] then args['label-posD'] = args['label-pos1'] end if not args['mark-size'..argval] then if args['show-geo-dots'] == '0' then args['mark-size'..argval] = args['mark-sizeD'] or '1' -- just use a 'point' for the label to attach to else if thisPop>0 then args['mark-size'..argval] = round(math.sqrt(thisPop/geoTotalPop*100) * 8 * scalefactor,0) else args['mark-size'..argval] = args['mark-sizeD'] or '1' args['shape-outline'..argval]=args['shape-outline'..argval] or args['shape-outlineH'] end end end local posTemp=string.lower(multiCheck(args,'label-pos',argval,'on')) args['label-pos'..argval] = posTemp posTemp=splitItem(posTemp,2)[1] if pvalue == 'P2046' then thisPop = round(thisPop,2) areatxt=sqkm..areatxt -- round decimals if sq km area elseif pvalue == 'P10622' then if mw.wikibase.getBestStatements(qvalue,pvalue)[1] then local punit=(mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.unit or ''):match("Q.*") -- extract Q value if punit == 'Q4917' then areatxt=' US$' elseif punit == 'Q4916' then areatxt=' euro' elseif punit == 'Q25224' then areatxt=' £GBP' else areatxt=mw.wikibase.getLabel(punit)..areatxt end end end if posTemp=='off' then else if not args['label'..argval] then args['label'..argval] = mw.wikibase.getLabel(qvalue) end local tline=posTemp:find("[%+%^%-]") if tline then local tval=posTemp:sub(tline,tline) local eline posTemp=posTemp:sub(1,tline-1) if posTemp == 'left' or posTemp == 'right' or string.find(posTemp,'east') or string.find(posTemp,'west') then if tval == '^' then eline = '^' else eline = ' ' end -- only add line break if 'forced', used for left/right elseif tval == '+' or tval == '^' then eline = '^' -- add linebreak unless 'forced' not to else eline = ' ' -- must be a dash, so no line break end args['label'..argval] = args['label'..argval]..eline..'('..formatInt(thisPop)..')' args['label-pos'..argval] = posTemp..args['label-pos'..argval]:sub(tline+1,#args['label-pos'..argval]) end end if posTemp=='on' then if argval=='1' and (not args.ldyD) then args.ldyD = args['ldy'..argval] or '0' end if not args['ldy'..argval] then args['ldy'..argval] = args.ldyD end -- ensure ldy for that dot is added to the position args['label-pos'..argval]='center' local _,br = string.gsub(args['label'..argval],'%^','') local item=splitItem(args['label-size'..argval],2) args['ldy'..argval] = (args['ldy'..argval] or 0) + ((item[1] or 11) * ((br+1)*0.6)) + ((args['geo-number-size'] or 12) / 2) end local nt='' if args['mark-title'..argval] == 'nolist' then nt='nolist,' end if not args['mark-title'..argval] or args['mark-title'..argval] == '' or args['mark-title'..argval] == 'nolist' then if mw.wikibase.getSitelink(qvalue) then args['mark-title'..argval] = nt..'[['..mw.wikibase.getSitelink(qvalue)..']]' else args['mark-title'..argval] = nt..mw.wikibase.getLabel(qvalue) end end -- If mark-title is not set locally then attempt Sitelink value, or use label if thisPop == 0 then areatxt='' end captionTableText[argval]={} captionTableText[argval][1]= args['mark-title'..argval] captionTableText[argval][2]= formatInt(thisPop) if maplist.width >= 280 then captionTableText[argval][3]= yearVal else captionTableText[argval][3]='no-year' end if args['show-q-values'] == '1' then captionTableText[argval][4]='<small>[[wikidata:'..qvalue..'|'..qvalue..']]</small>' else local nbsp = '&nbsp;' if maplist.width < 230 then nbsp = ' ' end captionTableText[argval][4]='' if captionTableText[argval][3] == 'no-year' then captionTableText[argval][2] = captionTableText[argval][2]..'<small><sup>'..nbsp..'[[wikidata:'..qvalue..'|WD]]</sup></small>' else captionTableText[argval][3] = captionTableText[argval][3]..'<small><sup>'..nbsp..'[[wikidata:'..qvalue..'|WD]]</sup></small>' end end if args['mark-title'..argval] == 'none' then -- no title else args['mark-title'..argval] = args['mark-title'..argval]..' ('..plabel..' '..formatInt(thisPop)..areatxt..')' end if not args['mark-description'..argval] then args['mark-description'..argval] = (mw.wikibase.getDescription(qvalue) or '')..'<br>' end if (not args['mark-image'..argval]) and (mw.wikibase.getBestStatements(qvalue,'P18')[1]) then args['mark-image'..argval] = mw.wikibase.getBestStatements(qvalue,'P18')[1].mainsnak.datavalue.value end item = multiCheck(args,'shape',argval,'circle') --default to circle if geo-data if not string.find(item,'svgpath,') then item=string.lower(item) end -- ensure svgpath items retain original case values else item = multiCheck(args,'shape',argval,'image') if not string.find(item,'svgpath,') then item=string.lower(item) end -- ensure svgpath items retain original case values end if string.find(item,'n-',0,true)==1 or string.find(item,'l-',0,true)==1 then autoDotTag=string.sub(item,0,1) item=string.sub(item,3) end if item == 'image' then shapeResult[nval].shape = 'image:' shapeResult[nval].shapeFile =multiCheck(args,'mark',argval,'Red pog.svg') shapeWidth=-1 else shapeResult[nval].shape = item end item= multiCheck(args,'mark-size',argval,(shapeResult[nval].shapeSize or '14px')) -- add geo-data as fallback if already assigned local a,b,c= getsize(string.gsub(string.gsub(item,',','px')..'px','pxpx','px')) if b==a and args['mark-dim'..argval] then b= b / tonumber(string.match(args['mark-dim'..argval],"[%.%-?%d]+")) end shapeHeight=b/2 item=tostring(a)..'px'..tostring(b)..'px'..tostring(c)..'px' shapeResult[nval].shapeSize= item itemTab=splitItem(multiCheck(args,'shape-color',argval,'hard red'),2) shapeResult[nval].shapeColor=itemTab[1] or 'hardred' item=itemCheck(itemTab[2],'%') -- jump through the various opacity hoops and add to color if needed if not item then item=itemCheck(args['shape-opacity'..argval],'%') end if item and item~='0%' and item~='100%' then shapeResult[nval].shapeColor=shapeResult[nval].shapeColor..item end shapeResult[nval].shapeAngle=itemCheck(multiCheck(args,'shape-angle',argval,'0'),'deg') or '0deg' if args['shape-angle'..argval] then shapeResult[nval].shapeAngleArg=true else shapeResult[nval].shapeAngleArg=false end --sort out the outline entry itemTab=splitItem(multiCheck(args,'shape-outline',argval,'transparent,0,100,solid'),4) shapeResult[nval].outlineColor=itemTab[1] or 'dark grey' shapeResult[nval].outlineWidth=itemCheck(itemTab[2],'px') or '1px' if itemTab[3] and itemTab[3]~='100' and itemTab[3]~='0' then shapeResult[nval].outlineColor=shapeResult[nval].outlineColor..itemCheck(itemTab[3],'%') end shapeResult[nval].outlineStyle=itemTab[4] or 'solid' -- label size, background, outline shapeResult[nval].textAT=args['label-attribute'..argval] or '' itemTab=splitItem( multiCheck(args,'label-size',argval,'12'),3) shapeResult[nval].textSZ=itemCheck(itemTab[1],'px') or '12px' if itemTab[2]=='outline' then shapeResult[nval].textBG=itemTab[3] or 'transparent' shapeResult[nval].textOL='1px' elseif itemTab[3]=='outline' then shapeResult[nval].textBG=itemTab[2] or 'transparent' shapeResult[nval].textOL='1px' else shapeResult[nval].textOL='0px' shapeResult[nval].textBG=itemTab[2] or 'transparent' end if getColor(shapeResult[nval].textBG)==CTB['hardgrey'] and shapeResult[nval].textBG~='hardgrey' then shapeResult[nval].textBG= 'transparent' end --label color etc itemTab=splitItem(multiCheck(args,'label-color',argval, 'darkgrey','label-colour'),2) shapeResult[nval].textCL=itemTab[1] or 'darkgrey' if itemTab[2] and itemTab[2]~='0%' and itemTab[2]~='100%' then shapeResult[nval].textCL=shapeResult[nval].textCL..itemTab[2] end shapeResult[nval].textSP=itemCheck( multiCheck(args,'label-spacing',argval,'0'),'px') -- sets letter-spacing in px shapeResult[nval].textLH=itemCheck( multiCheck(args,'label-height',argval,'120'),'%') -- sets line height, 120% default shapeResult[nval].textNG=itemCheck(multiCheck(args,'label-angle',argval,'0'),'deg') if args['label-angle'..argval] then shapeResult[nval].textNGarg=true else shapeResult[nval].textNGarg=false end --sgd=dotTag attributes shapeResult[nval].tagSize=(args['geo-number-size'] or tostring(shapeHeight*1.5))..'px' local c1,c2=checkColors(shapeResult[nval].shapeColor) shapeResult[nval].tagColor=c2 shapeResult[nval].tagSpacer='0px' shapeResult[nval].tagAngle='0deg' -- sge extension line attributes local shapePos=splitItem(multiCheck(args,'label-pos',argval,'right'),6) if shapePos[2]=='with-line' or shapePos[2]=='n-line' then shapeResult[nval].textEW=(shapePos[3] or '1')..'px' -- width shapeResult[nval].textEC=(shapePos[4] or shapeResult[nval].shapeColor or 'darkgrey') elseif shapePos[2]=='photo-panel' then shapeResult[nval].textEW='2px' -- width shapeResult[nval].textEC=shapeResult[nval].textCL else shapeResult[nval].textEW='0px' -- width shapeResult[nval].textEC='grey'-- colour end shapeResult[nval].textES='solid' if argval=='H' then return dotResult end --Assign dot values local dotItem={} dotItem.group=nval dotItem.code=nval dotItem.posType=shapePos[2] or 'nil' if (shapePos[2] or '') =='photo-panel' then dotItem.ppwidth= tonumber(string.match((shapePos[4] or '110'),"%d+")) dotItem.ppheight= tonumber(string.match((shapePos[5] or '48'),"%d+") ) dotItem.photowidth=round(tonumber(string.match((shapePos[3] or '1.3'),"[%.%-?%d]+")) * dotItem.ppheight+1,0) dotItem.photoImage=args['mark-image'..argval] --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end if (shapePos[2] or '') =='mark-line' then local x=tonumber(argval or '1') dotItem.markDest=shapePos[6] or tostring(x-1) dotItem.mlWidth= tonumber(string.match((shapePos[3] or '1'),"%d+")) dotItem.mlStyle= shapePos[4] or 'solid' dotItem.mlGap=tonumber(string.match((shapePos[5] or '0'),"[%d]+")) shapeResult[nval].textEC=shapeResult[nval].outlineColor or 'darkgrey' end if args['mark-coord'..argval] then itemTab=splitItem(convertCoordsTrad (args['mark-coord'..argval]),2) if itemTab[1] == nil or itemTab[2] == nil then debugmsg(mw.addWarning('Unable to read coordinates for mark-coord'..argval)) else dotItem.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) or 0 end else dotItem.lat=tonumber(string.match(args['mark-lat'..argval],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(args['mark-lon'..argval],"[%.%-?%d]+")) or 0 end if args['dateline'..argval] and (args['dateline'..argval]=='1' or args['dateline'..argval]=='-1') then dotItem.lon=dotItem.lon+(tonumber(args['dateline'..argval] ) *360) end maplist.lon=dotItem.lon maplist.lat=dotItem.lat dotItem.gridx, dotItem.gridy = maptogrid(maplist,1) local item=args['mark-title'..argval] or '' -- sort out the caption, wikilink and plaintext tooltip items from dotLink if item=='none' then dotItem.param1='nomap nolist' item='' end if string.find(item,'nolist') then dotItem.param1='nolist,' item = string.sub(args['mark-title'..argval],8) or args['label'..argval] end dotItem.dotLink=item if item ~= '' then item=stripdivs(item) dotItem.title=delink({item}) local linkstart= string.find(item,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then dotItem.dlink=delink({string.sub(item,linkstart,string.find(item,']]',1,true)+1),wikilinks='target'}) -- debugmsg('dlink for '..item..' is '..dotItem.dlink) else dotItem.dlink='' end else dotItem.dlink='' dotItem.title='' end if autoDotTag=='n' then item=nval elseif autoDotTag=='l' then item=string.char(64+tonumber(nval)) else item='' end dotItem.dotTag = args['numbered'..argval] or item if shapePos[2]=='n-line' and (args['label'..argval] or args['label'..argval]=='') then if dotItem.dlink =='' then item=dotItem.dotTag..' '..args['label'..argval] else item='[['..dotItem.dlink..'|'..dotItem.dotTag..']] '..args['label'..argval] end else item=(args['label'..argval] or '') end if args['labela'..argval] then item = item..'^'..args['labela'..argval] end if args['labelb'..argval] then item = item..'^'..args['labelb'..argval] end local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end dotItem.labelText = a -- convert hats to line breaks if argval=='' then item = (args['label-offset-x']) or (args.ldx) or '0' else item=args['label-offset-x'..argval] or args['ldx'..argval] or args['label-offset-xD'] or args.ldxD or args['label-offset-x1'] or args.ldx1 or '0' end dotItem.dx=tonumber(string.match(item,"[%.%-?%d]+")) if argval=='' then item = (args['label-offset-y']) or (args.ldy) or '0' else item=args['label-offset-y'..argval] or args['ldy'..argval] or args['label-offset-yD'] or args.ldyD or args['label-offset-y1'] or args.ldy1 or '0' end dotItem.dy=tonumber(string.match(item,"[%.%-?%d]+")) dotItem.labelPos=shapePos[1] if args['mark-image'..argval] then dotItem.imageName= args['mark-image'..argval] end dotItem.info=args['mark-description'..argval] or '' table.insert(dotResult,1,dotItem) -- add to start of list, so they are in reverese order for displaying -- debugmsg(dotItem.code..' = '..dotItem.lat..','..dotItem.lon..': '..dotItem.labelText..' - '..dotItem.title..', '..shapeResult[nval].shapeSize) return dotResult end local function tradstyleParseShapes(args,dotTable,dotmax) local sgNumbers,sgSortable={},{} for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['geo-data'..x] and (not args['mark-coord'..x]) and (not args['mark-lat'..x]) then -- get lat and lon values from wikidata and put them into the args.lat and args.lon for use wherever needed if (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1]) then args['mark-lat'..x] = (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1].mainsnak.datavalue.value.latitude) or '0' args['mark-lon'..x] = (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1].mainsnak.datavalue.value.longitude) or '0' local p=args['geo-data-type'] or 'P1082' if mw.wikibase.getBestStatements(args['geo-data'..x],p)[1] then local n = tonumber(mw.wikibase.getBestStatements(args['geo-data'..x],p)[1].mainsnak.datavalue.value.amount) or 0 if p == 'P2046' then -- area, so convert to sq km if needed (/100) n = convertToSqKm( n, mw.wikibase.getBestStatements(args['geo-data'..x],p)[1].mainsnak.datavalue.value.unit) end geoTotalPop = geoTotalPop + n end else debugmsg(mw.addWarning('could not find coords for '..mw.wikibase.getLabel(args['geo-data'..x])..', '..args['geo-data'..x])) end end if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) then sgNumbers[x]=x -- add it to the list end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form (needs a second table, seemingly) if args['mark-coord'] or (args['mark-lat'] and args['mark-lon']) then table.insert(sgSortable,'0') end -- add it to the end of the list local default={} default.D={} local dotResult={} for k,sgnum in pairs(sgSortable) do -- work through the sorted list, parsing each set of shapes in turn, from 1 upwards shapeList[sgnum]={} dotTable=assignTradstyleShape(shapeList,default,dotTable,args,sgnum) end dotTable=assignTradstyleShape(shapeList,default,dotTable,args,'H') -- construct an extra highlight shapeitem local unitConvert=1 local areaUnits='' if args['geo-region'] and (args['find-geo-dots'] or '1') ~= '0' then local region = args['geo-region'] -- or mw.wikibase.getEntityIdForCurrentPage() local pvalue = args['geo-data-type'] or 'P1082' local plabel = mw.wikibase.getLabel(pvalue) local subhead='' -- Assemble the subhead content, a line of text about the region as a whole if region and mw.wikibase.getBestStatements(region,pvalue)[1] then local punit=mw.wikibase.getBestStatements(region,pvalue)[1].mainsnak.datavalue.value.unit:match("Q.*") -- extract Q value if pvalue == 'P10622' then -- add other currency-based pvalues here areaUnits=mw.wikibase.getLabel(punit) end if punit == 'Q35852' then unitConvert=100 areaUnits = ' '..sqkm -- if its hectares, convert to sq km elseif punit == 'Q232291' then unitConvert = (1/2.59) areaUnits = ' '..sqkm -- or this if it's square miles elseif punit == 'Q81292' then unitConvert = 247.1 areaUnits = ' '..sqkm -- or this if it's acres elseif punit == 'Q712226' then unitConvert = 1 areaUnits = ' '..sqkm -- or this if it's already sq km elseif punit == 'Q4917' then areaUnits=' US$' -- replace long version with some shortform currencies () elseif punit == 'Q4916' then areaUnits=' euro' elseif punit == 'Q25224' then areaUnits=' £GBP' end -- if pvalue == 'P2046' then -- may need to add other instances of sq km so add in other P-items here if they need units showing -- end geoTotalPop=(mw.wikibase.getBestStatements(region,pvalue)[1].mainsnak.datavalue.value.amount) / unitConvert local yearVal if mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers then yearVal = mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P585[1].datavalue.value.time if yearVal and yearVal:sub(1,1) == '+' then yearVal = yearVal:sub(2,5) if mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459 and mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459[1] then local methodVal = mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459[1].datavalue.value.id if methodVal then yearVal=yearVal..' '..mw.wikibase.getLabel(methodVal) end end yearVal = ' ('..yearVal..')' else yearVal ='' end end subhead=totalItems..' '..mw.wikibase.getLabel(region)..' '..plabel..' ([[wikidata:'..region..']]) = '..formatInt(round(geoTotalPop),1)..areaUnits..yearVal else if pvalue == 'P2046' then areaUnits = sqkm end subhead=totalItems..' '..mw.wikibase.getLabel(region)..' '..plabel..' ('..sumOfDatapoints..') = '..formatInt(round(geoTotalPop),1)..areaUnits end if maplist.width < 350 and #plabel > 4 then plabel=string.sub((plabel or ' '),1,3)..'.' end if maplist.width < 330 then numberCol = ' ' end if maplist.width < 280 then year = 'no-year' end captionTable = subhead if (args['show-q-values'] or '') ~= '1' then captionTable = captionTable..'<div style="float:right;"><small> (WD&nbsp;=&nbsp;'..wikidataLink..')</small></div><br>' wikidataLink='' end captionTable = captionTable..'<table class="wikitable plainrowheaders sortable mw-collapsible mw-collapsed" style="width: 100%;"><caption>'..(args.toggletext or toggletext)..' </caption>' captionTable = htmlTableLine(captionTable,numberCol,mw.wikibase.getLabel(region),plabel..' '..areaUnits, year, wikidataLink,'header') end return dotTable end local function checkfortooltip (title,dx,dy,dotlabel,dlink,nolabel) -- returns tlink if available, and dlink, if needed and tshape=true if shape needed local tshape,tlink = false,"" if dlink~='' and not nolabel then tlink=dlink end if (tlink=="" or nolabel) and title~="" then tshape=true end -- tshape flags True if title is wanted for shape if (not (dx==0 or dy==0) or dotlabel=='') and title~='' then tshape=true end -- add tooltip to shape if its number has moved return tshape,tlink end local function tshift(angle) -- adjustment to place text near the centre of a triangle, shifted to allow rotation of triangle shape local x=tonumber(string.match(angle,"%-?%d+")) if x<0 then x=360+x end -- set to a single degree direction, 0 to 360 if x>359 then return 0,0 end -- shift the centre of the triangle based on rotation value if x <=40 or x>=320 then return -0.17,0 -- triangle up= -shiftv elseif x>=140 and x<=220 then return 0.17,0 --triangle down= +shiftv elseif x >220 then return 0,-0.17 --triangle left= -shifth elseif x >40 then return 0,0.22 --triangle right= +shifth end return 0,0 end local function makeTriangle(result,row,shape,outline,tlink) local w,h,r=getsize(shape.shapeSize) if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) w=w+p*2 h=h+p*2 end table.insert(result,'<div ') if tlink then table.insert(result,' title="'..row.title..'" ') end table.insert(result,'style="display:inline-block; position: absolute') if shape.shapeAngle ~= '0deg' then table.insert(result,'; transform: rotate('..shape.shapeAngle..')') end local shiftv,shifth=0,0 shiftv,shifth=tshift(shape.shapeAngle) table.insert(result,'; top: '..tostring(row.gridy-h/2+h*shiftv)..'px') table.insert(result,'; left: '..tostring(row.gridx-w/2+w*shifth)..'px; width: 0; height: 0; outline-width: 0px') table.insert(result,'; border-left: '..tostring(w/2)..'px solid transparent') table.insert(result,'; border-right: '..tostring(w/2)..'px solid transparent') if outline then -- fill with outline colour, to make a 'base layer' or shape colour table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.outlineColor).. '">') else table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.shapeColor).. '">') end table.insert(result,'</div>') end local function makeSquare(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) if shape.shape=='square' then h=w end -- squares are always square!. box can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end if r~=0 then div:css('border-radius',tostring(r).."px") end if shape.shape=='panel' then div:css('top', tostring(row.gridy).."px") div:css('left', tostring(row.gridx).."px") else div:css('top', tostring(row.gridy-h/2).."px") div:css('left', tostring(row.gridx-w/2).."px") end div:css('width', tostring(w).."px") div:css('height', tostring(h).."px") div:css('background-color', getColor(shape.shapeColor) ) div:css('color', 'inherit') table.insert(result,tostring(div)) end local function makeCircle(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding if shape.shape=='circle' then h=w end -- circles are always round. ellipse can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end div :css('top', tostring(row.gridy-h/2).."px") :css('left', tostring(row.gridx-w/2).."px") :css('width', tostring(w).."px") :css('height', tostring(h).."px") :css('border-radius', "50%") :css('background-color', getColor(shape.shapeColor) ) :css('color', 'inherit') table.insert(result,tostring(div)) end local function makeRuleA(result,row,shape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local lineV=0 if shape.shape=='rulea' then lineV=oWid*3+16 end table.insert(result,"<div style=\"display:inline-block; position: absolute") -- create a square transparent container, which will rotate line and arrow together table.insert(result,"; top:"..tostring(row.gridy - w/2).."px") table.insert(result,"; left:"..tostring(row.gridx - w/2).."px") table.insert(result,"; width:"..tostring(w).."px") table.insert(result,"; height:"..tostring(w).."px; background:transparent; color:inherit;") table.insert(result,"; transform: rotate( "..tostring(tonumber(string.match(shape.shapeAngle,"[%.%-?%d]+")) - 90).."deg);\">" ) table.insert(result,"<div style=\"display:inline-block; position: absolute") -- put the line (as a border-right) across the container table.insert(result,"; top:"..tostring(lineV).."px") table.insert(result,"; left:"..tostring((w - oWid )/2).."px; width: 0px") table.insert(result,"; height: "..tostring(w -lineV).."px") table.insert(result,"; border-right: "..shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) table.insert(result,"; background:transparent; color:inherit;\"></div>") if shape.shape=='rulea' then table.insert(result,"<div style=\"display:inline-block; position: absolute; top: 0px") --and add arrow head table.insert(result,"; left:"..tostring(w/2-( oWid/2)-oWid*0.55-2).."px; width: 0; height: 0; outline-width: 0px") table.insert(result,"; border-left: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-right: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-bottom: "..tostring(oWid*3+16).."px solid "..getColor(shape.outlineColor).."\"></div>") end table.insert(result,"</div>") end local function makeCurveA(result,row,shape) -- draw a curve with Arrow ----- local w,h=getsize(shape.shapeSize) -- = width,height local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local Angle=tonumber(string.match(shape.shapeAngle,"[%.%d]+")) table.insert(result,'<div style="position: absolute;') --set up out div, which will allow the whole to rotate table.insert(result,'top:'..tostring(row.gridy - (w + oWid*3+16)/2)..'px;') table.insert(result,'left:'..tostring(row.gridx - ( w + oWid*3+16)/2)..'px; ') table.insert(result,'width: '..tostring(w+oWid*3+16)..'px; ') table.insert(result,'height: '..tostring(w+oWid*3+16)..'px; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate( '..tostring(Angle-120)..'deg);">') else table.insert(result,'transform: rotate( '..tostring(Angle -62)..'deg);">') end table.insert(result,'<div style="position: absolute;') --set up div for the rounded corner of a rectangle table.insert(result,'border-left: '..shape.outlineWidth..' '..shape.outlineStyle..' '..getColor(shape.outlineColor)..';') if shape.shape=='curvea' then table.insert(result,'border-radius: 10000px 0 0 '..tostring(w)..'px; top:0px; left:'..tostring(w*0.25)..'px;') else table.insert(result,'border-radius: '..tostring(w)..'px 0 0 10000px;') table.insert(result,'top:'..tostring((oWid*3+16)/2+w*0.15)..'px; left:'..tostring(w*0.25)..'px;') end -- and add a triangular arrow head table.insert(result,'width: '..tostring(w)..'px; height: '..tostring(w)..'px;"></div><div style="position: absolute; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate(180deg); top:'..tostring(w-1)..'px; ') else table.insert(result,'transform: rotate(0deg); top: '..tostring(0-( ( oWid*3+16)/2)+1+( w*0.15) )..'px;') end -- reverse table.insert(result,'left:'..tostring(0-( oWid*0.6)-2+(w*0.25))..'px;') table.insert(result,'width: 0; height: 0; outline-width: 0px; border-left: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-right: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-bottom: '..tostring(oWid*3+16)..'px solid '..getColor(shape.outlineColor)..';"></div></div>') end local function makeLineTo (result,x1,y1,x2,y2,oWid, oStyle, oCol,double,shape) table.insert(result,"<div style=\"display:inline-block; position: absolute;") -- draw a line between x1,y1 and x2,y2, px-coords where 0,0 is centre of frame -- Maths calculations thanks to ES local tw if double <= 1 then tw = tonumber(string.match(oWid,"[%.%d]+"))/2 -- shift centre by half the line width else tw = tonumber(string.match(oWid,"[%.%d]+")) + (double/2) - 0.5 -- shift centre by a line width + half the gap end -- debugmsg(oWid..'+'..double..'/2='..tw) table.insert(result,"left: "..tostring(x1+( (x2-x1)/2) - (math.sqrt( ( x2-x1)^2 + (y2-y1)^2 )/2) ).."px;") table.insert(result,"top: "..tostring(y1+( ( y2-y1 )/2) - tw).."px;") table.insert(result,"width: "..tostring(math.sqrt( (x2-x1 )^2 + ( y2-y1 )^2) ).."px;") table.insert(result,"height: "..tostring(double).."px; background-color:transparent; color:inherit; ") table.insert(result,"outline-width: 0; border-bottom: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) if double>1 then table.insert(result,"border-top: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) end local lineAngle=math.atan(( y2-y1)/( x2-x1 ) )*180/math.pi -- if x1 == x2 then lineAngle=90 end table.insert(result,"transform: rotate("..lineAngle.."deg);\"></div>") -- debugmsg('text angle started as '..shape.textNG..' and lineAngle = '..lineAngle) if (lineAngle < -90) or (lineAngle >90) then lineAngle=lineAngle-180 end if shape and not shape.textNGarg then shape.textNG=tostring(round(lineAngle,0))..'deg' end -- Put in line angle for a default text angle as well -- if (y2 >= y1) and (x2 <= x1) then lineAngle=lineAngle-180 debugmsg('arrow angle corrected') end if x2 < x1 then lineAngle=lineAngle-180 end if shape and not shape.shapeAngleArg and shape.shape == 'arrowhead' then shape.shapeAngle=tostring(round(lineAngle,0))..'deg' end -- line angle for arrowhead -- debugmsg('text angle is now '..shape.textNG..'with arrow at '..lineAngle) end local function makeClipPath(result,row,shape,outline,tshape) --tshape is a flag to show if the tooltip (title=) is wanted -- return the text css div code to position and draw a shape occupying a specified clippath if string.find(shape.shape, 'svgpath,') and #shape.shape > 9 then pathshape.svgpath=string.sub(shape.shape,9) shape.shape='svgpath' end if not pathshape[shape.shape] then debugmsg(mw.addWarning('shape'..row.code..' = '..shape.shape..'. The shape name is not defined')) return end local w,h,r=getsize(shape.shapeSize) if shape.shape == 'arrowhead' then -- enlarge arrowheads, as they only go to the middle of the notional shape-size w=w*2.5 h=h*2.5 end if string.match(shape.shape,"circle") or string.match(shape.shape,"square") then h=w end -- use ellipse and box for stretched shapes local shifth,shiftv = 0,0 if string.match(shape.shape,"triangle") then shiftv,shifth =tshift(shape.shapeAngle) end if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) or 0 w=w+p*2 h=h+p*2 end table.insert(result,"<div ") if tshape then -- Add tooltip if needed table.insert(result," title=\""..row.title.."\" ") end table.insert(result,"style=\"display:inline-block; position: absolute; background-color:") if outline then table.insert(result,getColor(shape.outlineColor)) -- fill with outline colour, to make a 'base layer' else table.insert(result,getColor(shape.shapeColor)) end table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;"..pathshape[shape.shape].."&#39;) ") -- adds the required clippath data from the table of pathshape string literals table.insert(result,"; top:"..tostring(row.gridy - 10 + h*shiftv).."px") table.insert(result,"; left:"..tostring(row.gridx - 10 + w*shifth).."px") table.insert(result,"; width:20px") -- needs to be a path within a 20px20px box, and then rescales using size values to match other shape sizes table.insert(result,"; height:20px; transform:") if shape.shapeAngle ~= "0deg" then table.insert(result," rotate("..shape.shapeAngle..")") end table.insert(result," scale("..tostring(w/16)..", "..tostring(h/16)..")") table.insert(result,"\"></div>") end local function makeImage(result,row,shape) local w,h,r=getsize(shape.shapeSize) local image=shape.shapeFile if not image or image=='' then image='Red pog.svg' end local imagediv=mw.html.create ('div') imagediv:css('position', "absolute") if shape.shapeAngle ~= "0deg" then imagediv:css('transform',"rotate("..shape.shapeAngle..")") end imagediv :css('top', (row.gridy+2 + math.min(h/2-12,0) - h/2).."px") --File seems to adjust pos for small images :css('left', (row.gridx-1-w/2).."px") :css('background-color', "transparent" ) :css('color','inherit') :wikitext('[[file:'..image..'|'..tostring(w+2)..'px|alt='..(row.title or '')..'|link=]]') table.insert(result,tostring(imagediv)) end local function makePhotoPanel(result,row,shape) local h=row.ppheight table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;' ) table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') table.insert(result, 'width: '..tostring(row.ppwidth)..'px; height: '..tostring(h)..'px; border-radius: 2px; color:inherit;') table.insert(result, 'background-color: #E8E8D6; outline: 2px solid '..getColor(shape.textCL)..'; box-shadow: 2px 2px 4px #33203335;"></div>') if row.photoImage and row.photowidth >0 then table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;') if row.labelPos=='left' or string.find(row.labelPos,'west') then table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') row.dx=row.dx+row.photowidth/2 else table.insert(result, 'left: '..tostring(row.gridx+row.dx + (row.ppwidth-row.photowidth) - row.ppwidth/2)..'px;') row.dx=row.dx-row.photowidth/2 end table.insert(result, 'background-color:transparent; color:inherit; border-radius: 2px;">') table.insert(result,'[[File:'..row.photoImage..'|x'..tostring(h)..'px|File:'..row.photoImage..']]</div>') row.labelPos='center' end end local function makePanelText(result, row, shape) local w,h,r=getsize(shape.shapeSize) local ty=tonumber(string.match(shape.textSZ or '11',"%d+") ) table.insert(result,'<div style="position:absolute; line-height: 120%; font-size: '..shape.textSZ..'; color:'..getColor(shape.textCL)..';') if row.labelPos == 'left' or string.find(row.labelPos,'west') then table.insert(result,'top: '..tostring(row.dy+row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+3)..'px; text-align: left; width:'..tostring(w)..'px;') elseif row.labelPos == 'right' or string.find(row.labelPos,'east') then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+w-3)..'px; text-align: right; width: max-content; transform: translateX(-100%);') elseif row.labelPos == 'top' or row.labelPos== 'north' then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') elseif row.labelPos == 'bottom' or row.labelPos=='south' then local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*1.1 table.insert(result,'top: '..tostring(row.gridy+w-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') else -- center or centre local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*0.6 table.insert(result,'top: '..tostring(row.gridy+(h/2)-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') end if shape.textSP and shape.textSP ~='0px' then table.insert(result,"letter-spacing: "..shape.textSP..';') end if shape.textLH and shape.textLH ~='120%' then table.insert(result,"line-height: "..shape.textLH..';') end table.insert(result,"vertical-align: middle;\">"..row.labelText.."</div>") end local function makeTextItem(result, row, shape, align, tlink, textItem, dotItem) local w,h,r=getsize(shape.shapeSize) table.insert(result,"<div ") if row.title ~= "" then table.insert(result," title=\""..row.title.."\" ") end local ty,bry,linkoffset = 0,0,0 local compy=0 local lh=tonumber(string.match(shape.textLH or '120',"%d+"))/100 if dotItem==1 or (dotItem==2 and row.posType~='n-line') then -- if there is a dotTag in the middle of the shape then use the tag settings ty=tonumber(string.match(shape.tagSize,"%d+")) or 0 table.insert(result,"style=\"position:absolute; line-height: 120%; top: "..tostring(row.gridy-ty*0.62)) table.insert(result,"px; left: "..tostring(row.gridx).."px; width: fit-content; ") table.insert(result,"text-align: center; color: "..getColor(shape.tagColor).."; background-color: transparent;") local trf="" if shape.tagAngle ~="0deg" then trf=" rotate("..shape.tagAngle..")" end table.insert(result, "transform: translateX(-50%)"..trf.."; font-size: "..shape.tagSize..";") if shape.tagSpacer~='0px' then table.insert(result, "letter-spacing:"..shape.tagSpacer..";") end else -- or add tfx settings for left, right or center align, colors, backgrounds, border-outline table.insert(result,'style="position:absolute; ') if dotItem==2 then -- dotTag is out at x,y so 85% ty=tonumber(string.match(shape.tagSize or '0',"%d+")) table.insert(result,'font-size: '..shape.tagSize..'; padding:0px 2px;line-height: 85%; top: '..tostring(row.dy+row.gridy-ty*0.52)) else -- it is labelText, so use textLH or 120% ty=tonumber(string.match(shape.textSZ or '11',"%d+")) if row.labelPos=='northwest' or row.labelPos=='northeast' then compy=-ty elseif row.labelPos=='southeast' or row.labelPos=='southwest' then compy=ty/2 end if row.labelPos and not (row.labelPos== 'auto' or row.labelPos=='') then bry=(select(2, string.gsub(row.labelText,"<br>", ""))*lh) -- is it a multiline text? expand by line-height /120%? if row.posType == 'with-line' then bry=0 end if row.labelPos=='bottom' or row.labelPos == 'south' then bry = 0 -- and shift by none, all or half elseif row.labelPos=='top' or row.labelPos == 'north' then if shape.shape=='image:' then bry= 1 + math.min(w/2-10,0)-bry*ty else bry= -bry*ty+2 end else bry=-bry*(ty/2 * lh) end end if row.posType == 'photo-panel' then bry=bry+3 end table.insert(result,'font-size: '..shape.textSZ..'; padding:0px 3px;line-height: '..shape.textLH..'; ') table.insert(result,'top: '..tostring(row.dy+row.gridy+compy+bry-ty*lh/2)) end table.insert(result,"px; left: "..tostring(row.dx+row.gridx).."px; color: "..getColor(shape.textCL).."; ") table.insert(result,"width: max-content; ") local trf="" if shape.textNG ~="0deg" then trf="rotate("..shape.textNG..")" end if shape.textOL~="0px" then table.insert(result,"background-color: "..getColor(shape.textBG).."; ") table.insert(result,"border: "..shape.textOL.." solid "..getColor(shape.textCL).."; border-radius:6px;") else table.insert(result,"background-color: transparent;") end if row.labelPos=="right" or string.find(row.labelPos,'east') then table.insert(result,"text-align: left; ") linkoffset=w if shape.textNG ~="0px" then table.insert(result,"transform-origin: left; transform: rotate("..shape.textNG.."); ") end elseif row.labelPos=="left" or string.find(row.labelPos,'west') then if shape.textNG ~="0px" then table.insert(result,"transform-origin: right;") end linkoffset=-w table.insert(result,"text-align: right; transform: translateX(-100%) "..trf.."; ") else table.insert(result,"text-align: center; transform: translateX(-50%) "..trf.."; ") end table.insert(result,'font-weight: normal; line-height: '..shape.textLH..'; letter-spacing:'..shape.textSP..'; vertical-align: bottom;') end if string.find(shape.textAT or '','bold') then textItem='<b>'..textItem..'</b>' end if string.find(shape.textAT or '','italic') then textItem='<i>'..textItem..'</i>' end if shape.textOL=='0px' and shape.textBG~='transparent' and dotItem==0 then table.insert(result,'\"><span style=\"background-color: '..getColor(shape.textBG)..'; color:inherit;\">'..textItem..'</span></div>') else table.insert(result,"\">"..textItem.."</div>") end if tlink~='' and not string.match(row.param1 or "","nolink") then table.insert(result,makeLinkBox(row.gridx+row.dx+linkoffset, row.gridy+row.dy+bry, 16, row.dotTag..' '..row.title,tlink)) end end local function getshapetable(row,shape) -- Construct CSS divs for a dot from shape and map data local result={} local w,h,rndg=getsize(shape.shapeSize) local tshape,tlink=checkfortooltip(row.title,row.dx,row.dy,row.dotTag,row.dlink,string.match(row.param1 or "","nolink") ) local align=row.labelPos or '' local offsetx,offsety=0,0 local ty=tonumber(string.match((shape.tagSize or 9),"%d+" )) if row.labelText and row.labelText~='' then ty=tonumber(string.match((shape.textSZ or 11),"%d+" )) else align='center' end -- it is just for dotTag, so justify center -- identify align value and extend offsets local widthzone,heightzone = (w/2)+1,(h/2)+1 local theta,r = math.deg( math.atan2(row.dy, row.dx)) , math.sqrt(row.dx^2 + row.dy^2) if align=='auto' or align=='' then if (theta < -112 or theta > 112) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "left" offsetx=1 elseif (theta > -68 and theta < 68) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "right" offsetx=-1 elseif theta <0 then offsety=ty/2-1 -- bottom else offsety=0-ty/2+1 -- top end elseif align=='left' or string.find(align,'west') then row.dx=row.dx - w/2 widthzone=4 offsetx=-1 elseif align=='right' or string.find(align,'east') then row.dx=row.dx + w/2 widthzone=4 offsetx=1 elseif align=='top' or align=='north' then if string.find(shape.shape,'curve') then row.dy=row.dy-(h/2)-12 heightzone=4 offsety=ty/2-1 else row.dy=row.dy - h-2 heightzone=4 offsety=ty/2-1 end elseif align=='bottom' or align=='south' then if string.find(shape.shape,'curve') then row.dy=row.dy+(h/2) heightzone=4 offsety=ty/2-1 else row.dy=row.dy + h+2 heightzone=4 offsety=0-ty/2-1 end end if shape.textEW ~= "0px" and not string.match(row.param1 or "","noline") then -- debugmsg('r-(w/2) > widthzone r-w/2='..tostring(r-(w/2))..', widthzone='..widthzone..', label='..(row.labelText or '')..', postype='..(row.posType or '')) makeLineTo(result, row.gridx+1, row.gridy-1, row.gridx+row.dx+offsetx, row.gridy+row.dy+offsety, shape.textEW, shape.textES,shape.textEC,1) -- debugmsg('n-line for '..row.code..':'..tostring(row.gridx+1)..', '..tostring(row.gridy-1)..', '..tostring(row.gridx+row.dx+offsetx)..', '..tostring(row.gridy+row.dy+offsety)..', with width '..tostring(shape.textEW)..'px '..(shape.textES or 'no style')..' and color '..shape.textEC) end if row.posType and row.posType=='mark-line' then if row.gridx2 and row.gridy2 then makeLineTo(result, row.gridx, row.gridy, row.gridx2, row.gridy2, tostring(row.mlWidth)..'px', row.mlStyle, getColor(shape.textEC),row.mlGap,shape) -- debugmsg('mark-line line drawn from '..row.code..' with width '..tostring(row.mlWidth)..'px '..(row.mlStyle or 'no style')..' and color '..getColor(shape.textEC)) end end if w ~= 0 then if shape.shape=='itriangle' then shape.shape='triangle' shape.shapeSize=tostring(w)..'px'..tostring(w/2)..'px' end if shape.shape=="triangle" then if shape.outlineWidth ~= "0px" then makeTriangle(result,row,shape,true,false) -- larger triangle to give the outline, if required end makeTriangle(result,row,shape,false,tshape) -- smaller triangle to fit over the top elseif shape.shape=="square" or shape.shape=="box" or shape.shape=='panel' then makeSquare(result,row,shape,tshape) elseif shape.shape=="circle" or shape.shape=="ellipse" then makeCircle(result,row,shape,tshape) elseif string.find(shape.shape,'image:')==1 then makeImage(result,row,shape) elseif shape.shape=="rulea" or shape.shape=='rule' then makeRuleA(result,row,shape) elseif shape.shape=="curvea" or shape.shape=="curvec" then makeCurveA(result,row,shape) else -- use a pathshape clipPath if shape.outlineWidth ~= "0px" then makeClipPath(result,row,shape,true,false) -- larger path-shape to give the outline, if required end makeClipPath(result,row,shape,false,tshape) end end if row.ppwidth and row.ppwidth>0 then makePhotoPanel(result,row,shape) end if shape.shape=='panel' and row.labelText then makePanelText(result, row, shape) else if row.dotTag and row.dotTag ~= "" then -- there is a dotTag if (row.dx==0 and row.dy==0) and w>0 then -- it is on the dot so if dotsize is not 0 any label is ignored makeTextItem(result, row, shape, align, tlink, '<b>'..row.dotTag..'</b>', 1) else if row.labelText and row.labelText~='' then -- tag and label both used makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.labelText, 0) else makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.dotTag, 2) -- tag is ouside the dot end end else if (row.labelText and row.labelText~='') then -- just the label. No tag makeTextItem(result, row, shape, align, tlink, row.labelText, 0) end end end if tlink and tlink~='' then table.insert(result,makeLinkBox(row.gridx, row.gridy,w+3,row.dotTag..' '..row.title,tlink)) end return table.concat(result) end local function getmapframecontent(args,use) local result, comma= {}, '' local propertyTable= {} local color=splitItem(string.lower( args['map-data-color'] or 'dark orange'), 2) if use == 'basemap' then table.insert(result, '[') propertyTable.stroke = "#000000" propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') if color[2] then propertyTable['stroke-opacity'] = tonumber(color[2] or '20')/100 end else propertyTable.title = args['map-data-text'] or args['map-data'] or '' propertyTable.stroke = getColor(color[1]) propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') propertyTable['stroke-opacity'] = tonumber(color[2] or '100')/100 end if args['map-data-inverse'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geomask', ids = args['map-data-inverse'], properties = {title = (args['map-data-text'] or ''), stroke = '#555555', fill = '#555555', ['fill-opacity'] = 0.1, ['stroke-width'] = 1, ['stroke-opacity'] = 0.5 } } table.insert(result, mapJson) comma=', ' end if args['map-data-heavy'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-heavy'], properties = { title = args['map-data-heavy'], stroke = propertyTable.stroke, ['stroke-width'] = 9, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.2 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data-light'] and args['map-data-light'] ~= '' then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-light'], properties = { title = args['map-data-light'], stroke = propertyTable.stroke, ['stroke-width'] = 3, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.4 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data'] then propertyTable['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.2 local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data'], properties = propertyTable } table.insert(result, comma .. mapJson) comma=', ' end if args['map-wdqs'] then -- inserts a SPARQL wdqs query to find geopoints, geoshapes etc from wikidata local mapSparql = mw.text.jsonEncode{ type = 'ExternalData', service = args['map-wdqs-type'] or 'geopoint', query = args['map-wdqs'], } table.insert(result, comma .. mapSparql) -- debugmsg('mapSparql = ' .. mapSparql) comma=', ' end if args['map-raw'] then table.insert(result, comma .. args['map-raw']) end if use=='basemap' then table.insert(result,']') end return table.concat(result) end --eg | minilocator=filename,bottom right,132px153px, 38%,60%, 22px local function makeLocatorMap (args, result) local miniFile,pos,itemlist,miniW,miniH, miniX,miniY,miniBox, miniBH if args['mini-locator'] then pos,miniFile=extractItem(args['mini-locator']) -- first item is filename, in quotes if it includes commas itemlist=splitItem(pos,5) -- put items in a table filename removed, position,WpxHpx, x%y%,box pos=itemlist[2] or 'right' miniW,miniH=getsize(itemlist[3]) miniX=tonumber(string.match(itemlist[4] or '0','[%d]+'))*miniW/100 miniY=tonumber(string.match(itemlist[5] or '0','[%d]+'))*miniH/100 miniBox=tonumber(string.match(itemlist[6] or '0','[%d]+')) miniBox=miniBox*miniW/100 miniBH=miniBox * maplist.height/maplist.width elseif args['mini-file'] then miniFile = args['mini-file'] pos=string.lower(args.minimap or 'right') if pos=='file' then pos='right' end miniW,miniH = tonumber(args['mini-width'] or 60), tonumber(args['mini-height'] or 60) -- find top left corner of locator miniBox,miniBH=tonumber(args['minimap-boxwidth'] or '0'),0 -- firm up pos offsets for dot (with % or not) and boxsize if any, miniX, miniY=args['minipog-gx'],args['minipog-gy'] if not miniX then miniX=tonumber(args['minipog-x'] or '0') else miniX=tonumber(miniX)*tonumber(miniW)/100 end if not miniY then miniY=tonumber(args['minipog-y'] or '0') else miniY=tonumber(miniY)*tonumber(miniH)/100 end if args['minipog-gx'] then miniBox=miniBox*miniW/100 end miniBH=miniBox * maplist.height/maplist.width else return end local miniTop,miniLeft=0,1 if not string.find(pos,'top') then --only use top left, as link box is in top right or put bottom left or right miniTop=maplist.height+2-miniH if string.find(pos,'right') then miniTop=miniTop-15 -- to avoid (c) line miniLeft=maplist.width-1-miniW end end table.insert(result,'<div style="position: absolute; outline-width: 1px; outline-style: solid; outline-color: white;') table.insert(result,'top: '..tostring(miniTop)..'px; left:'..tostring(miniLeft)..'px; width:'..tostring(miniW)..'px;' ) table.insert(result,'background-color:transparent; color:inherit;">[[File:'..miniFile..'|'..tostring(miniW)..'px|File:'..miniFile..']]</div>') if miniX and miniX>0 then if args['minipog-y'] then miniY=miniY/1.04 end if args['minipog-x'] then miniX=miniX/1.04 end if miniBox<1 then table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-3-6)..'px; left:'..tostring(miniX+miniLeft-3)..'px;') table.insert(result, 'width: 6px; background-color:transparent; color:inherit;">[[File:Red pog.svg|6px|link=]]</div>') else table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-miniBH/2)..'px; left:'..tostring(miniX+miniLeft-miniBox/2)..'px;') table.insert(result, 'width: '..tostring(miniBox)..'px; height:'..tostring(miniBH)..'px; outline:1px solid #AA1205; background-color:#AA120522; color:inherit;"></div>') end end end local function makeArcText(args,result,nval) local items, itemlist='',{} local arcText='' if args['arc'..nval] then items=convertCoords (args['arc'..nval]) items,arcText=extractItem(items) -- first item is text, in quotes if it includes commas itemlist=splitItem(items,9) -- put items in a table: text, lat,lon,size,color,angle,gap,radius,ellipse if itemlist[4]=='' then itemlist[4]='12' end itemlist[4]=string.match((itemlist[4] or '12'),"[%.%-?%d]+") if itemlist[5]=='' then itemlist[5]='grey' end itemlist[6]=string.match((itemlist[6] or '0'),"[%.%-?%d]+") itemlist[7]=string.match((itemlist[7] or '0'),"[%.%-?%d]+") itemlist[8]=string.match((itemlist[8] or '0'),"[%.%-?%d]+") end if args['arc-coord'..nval] then local itemTab=splitItem(convertCoordsTrad (args['arc-coord'..nval]),2) maplist.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) maplist.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) else maplist.lat=tonumber(string.match(args['arc-lat'..nval] or itemlist[2] or '0',"[%.%-?%d]+")) maplist.lon=tonumber(string.match(args['arc-lon'..nval] or itemlist[3] or '0',"[%.%-?%d]+")) end local arcX,arcY=maptogrid(maplist,6) arcText = args['arc-text'..nval] or arcText local fontSize =tonumber(args['arc-text-size'..nval] or itemlist[4] or '12') local textColor=getColor(string.gsub(args['arc-text-color'..nval] or itemlist[5] or 'grey','[%s]+','') ) local arcAngle= tonumber((args['arc-angle'..nval]) or (itemlist[6]) or '45')-90 local arcRadius =tonumber(args['arc-radius'..nval] or itemlist[8] or '0.05') local arcGap = tonumber(args['arc-gap'..nval] or itemlist[7] or '1')* ( ( math.sin(8-math.rad(arcRadius))^8 )+0.4 )*( ( fontSize+6 )/15 ) arcRadius=450*arcRadius*0.75 local ellipseFactor=tonumber(args['ellipse-factor'..nval] or itemlist[9] or '1') local arcRotate =arcAngle+90 if arcGap<0 then arcRotate=arcAngle-90 end local latF=arcY - fontSize + (0-(math.sin(math.rad(arcAngle))) * arcRadius) local lonF=arcX - fontSize +(0-(math.cos(math.rad(arcAngle))) * arcRadius) local step=1 for codepoint in mw.ustring.gcodepoint( arcText ) do -- block step=1,#arcText do table.insert(result,'<div style="position: absolute;') local posY=tostring(round( (latF + (math.sin(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius)) *ellipseFactor,2))..'px;' local posX=tostring(round( (lonF + (math.cos(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius))/ellipseFactor,2))..'px;' table.insert(result,' top: '..posY..' left: '..posX..' transform: rotate( '..tostring(round(arcRotate +((step-1)*arcGap)),2)..'deg);') table.insert(result,'width:'..tostring(fontSize*2)..'px; text-align: center; background-color:transparent; color: '..textColor..';') table.insert(result,'vertical-align: baseline; font-size: '..tostring(fontSize)..'px;">'..mw.ustring.char(codepoint)..'</div>') step=step+1 end end local function makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) local item={} itemdescription=stripdivs(itemdescription or '') local templon=lon if lon > 180 then templon=lon-360 end --for hemisphere+ or -1 dots if lon < -180 then templon=lon+360 end -- use 'real' coordinates for geohack label, while retaining shifted coords for plot if itemcolor=='transparent' then itemcolor='white' end itemcolor=getColor(itemcolor) -- ensure no opacity, which breaks maplink if string.find(itemcolor,'#')==1 and #itemcolor>7 then itemcolor=string.sub(itemcolor,1,7) end table.insert(item, '{ "type": "Feature", "properties": {') table.insert(item, ' "title": "'..itemtitle..'",') table.insert(item, ' "description": "'..itemdescription) table.insert(item, ' ([https://geohack.toolforge.org/geohack.php?params='..tostring(lat)..';'..tostring(templon)) table.insert(item, '_dim:2000 '..tostring(lat)..','..tostring(templon)..'])",') table.insert(item, ' "marker-symbol": "-number-'..string.gsub(group,'%W','')..'", "marker-size": "medium", "marker-color": "'..itemcolor..'" },') table.insert(item, ' "geometry": {"type": "Point", "coordinates": ['..tostring(lon)..','..tostring(lat)..'] } }') return table.concat(item) end local function makeLegendBox(result,args) local legend ={} local line,count, maxWidth='',1,8 local item line,item=extractItem(args.legendBox or '') local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end legend.Text = a -- convert hats to line breaks line=splitItem(line,6) -- (text, size, poition,background color, text/outline color, param options) legend.Size=line[2] or '150px80px1px' legend.Pos=line[3] or '10px10px' legend.Background=line[4] or 'beigeground' legend.Color=line[5] or 'darkbrown' legend.Param= line[6] or '' local argnum,legendCount,titleHeight='1',1,0 if (legend.Text and legend.Text~='') then titleHeight=15+(13.4*(select(2, string.gsub(legend.Text,"<br>", "")))) end local legendLine,legendGroup,legendY={},{},{} while args['legendItem'..argnum] do -- assign legendLine, legendGroup, legendY for each dot line,legendLine[legendCount] = extractItem(args['legendItem'..argnum] or '') line=splitItem(line,3) legendGroup[legendCount]=line[2] or argnum if shapeList[legendGroup[legendCount]] then maxWidth=math.max(tonumber(string.match(shapeList[legendGroup[legendCount]].shapeSize or '10','[%d]+')),maxWidth) else maxWidth=math.max(tonumber(string.match(shapeList['1'].shapeSize or '10','[%d]+')),maxWidth) end maxWidth=maxWidth+1 if line[3] then legendY[legendCount]=tonumber(string.match(line[3],'[%d]+')) else legendY[legendCount] = 3+maxWidth*(legendCount-1)+titleHeight -- if (legend.Text and legend.Text~='') then legendY[legendCount]=legendY[legendCount]+15 end end legendCount=legendCount+1 argnum=tostring(legendCount) end local w,h,r=getsize(legend.Size or '') local x,y=getsize(legend.Pos or '') local div=mw.html.create ('div') div:css('position', 'absolute') div:css('outline', '1px solid'..getColor(legend.Color)) if r~=0 then div:css('border-radius',tostring(r).."px") end div :css('top', y.."px") :css('left', x.."px") :css('width', w.."px") :css('height', h.."px") :css('line-height','105%') :css('background-color', getColor(legend.Background) ) :css('color','inherit') if not string.find(legend.Param,'noshadow') then div:css('box-shadow', '2px 2px 4px #33203335') end div:tag( 'div' ) :css('position', 'absolute') :css('top','1px') :css('left', (w/2).."px") :css('width',(w-8)..'px') :css('text-align', 'center') :css('color', getColor(legend.Color)) :css('transform', 'translateX(-50%)') :css('font-size','11px') :wikitext(legend.Text) :done() for lct=1,legendCount-1 do --local t=legendGroup[lct] local shape=shapeList[legendGroup[lct]] or shapeList['1'] local row={} row.gridx=3+maxWidth/2 row.gridy=(legendY[lct] or 0) + 5 if shape.shape=='image:' then row.gridy=row.gridy+6 end row.dx=0 row.dy=0 local legendShape= getshapetable(row,shape) div:wikitext(legendShape) :tag( 'div' ) :css('position', 'absolute') :css('top',(legendY[lct] or 0)..'px') :css('left', (maxWidth+6)..'px') :css('width', (w-maxWidth-6)..'px') :css('text-align', 'left') :css('line-height','103%') :css('color', getColor(legend.Color)) :css('font-size','10px') :wikitext(legendLine[lct]) :done() end div:allDone() table.insert(result,tostring(div)) end local function getQset (qValue,pValue,argNumber,args) local result={} local sortType=string.lower(args['geo-list-sort'] or 'a') local testValue='0' if sortType == 'alpha' or sortType == 'a' or sortType == 'ah' or sortType == 'al' then testValue = mw.wikibase.getLabel(qValue) elseif sortType == 'value' or sortType == 'v' or sortType == 'vh' or sortType == 'vl'then if qValue==nil or pValue == nil then debugmsg(mw.addWarning((qValue or 'nil')..' Qnumber for '..(pValue or 'nil'))) else if mw.wikibase.getBestStatements(qValue,pValue)[1] then testValue = mw.wikibase.getBestStatements(qValue,pValue)[1].mainsnak.datavalue.value.amount or '0' if pValue == 'P2046' then testValue = convertToSqKm(testValue, mw.wikibase.getBestStatements(qValue,pValue)[1].mainsnak.datavalue.value.unit) end end end elseif sortType == 'north' or sortType == 'n' or sortType == 'nh' or sortType == 'nl' then if mw.wikibase.getBestStatements(qValue,'P625')[1] then testValue = mw.wikibase.getBestStatements(qValue,'P625')[1].mainsnak.datavalue.value.latitude or 0 end elseif sortType == 'west' or sortType == 'w' or sortType == 'wh' or sortType == 'wl' then if mw.wikibase.getBestStatements(qValue,'P625')[1] then testValue = mw.wikibase.getBestStatements(qValue,'P625')[1].mainsnak.datavalue.value.longitude or 0 end end result[1]=testValue result[2]=argNumber return result end function p._main ( args ) local result={} local frame=mw.getCurrentFrame() local dotTable={} local magVal,scaleVal = args.magnify,'' local origH,origW=maplist.height,maplist.width if magVal then --set up the values needed to magnify the top-right portion of the map magVal=tonumber(string.match(magVal or '0',"[%.%-?%d]+")) or 1 if magVal>1 and magVal <=2 then maplist.height= round(maplist.height/magVal,0) maplist.width=round(maplist.width/magVal,0) scaleVal='transform: scale('..magVal..') translateY('..tostring((origH-maplist.height)/2)..'px);' else magVal=1 end end -- set up the three nested div boxes (plus an extra if centered) to put the map plus title/caption area, in an appropriate frame on the page if args.float=='center' or args.float=='centre' then table.insert(result,'<div class="center"><div class="thumb tnone">') elseif args.float=='left' then table.insert(result,'<div class="thumb floatleft">') else table.insert(result,'<div class="thumb floatright">') end table.insert(result,'<div class="thumbinner" style="position: relative; top: 0px; right: 0px; width: '..(args.width or "400")..'px;">') if args.title then table.insert(result,'<div class="center" style="font-weight:bold">'..args.title..'</div>') end if magVal and magVal >1 and magVal<=2 then table.insert(result,'<div class="thumbinner noresize" style="display:block; position: relative; outline:0px; border:0px; padding:0px; background-color:transparent; color:inherit;') table.insert(result,'top: 50%; right: '..tostring(0-((origW-maplist.width))/2)..'px; ') table.insert(result,'height: '..origH..'px; width: '..maplist.width..'px; '..scaleVal..'">') else magVal=1 table.insert(result,'<div class="thumbinner noresize" style="position: relative; outline:0px; border:0px; padding:0px;') table.insert(result,'top: 0px; right: 0px; ') table.insert(result,'height: '..maplist.height..'px; width: '..maplist.width..'px ">') end --add any boundary Qvalues to map-data-light if requested if args['geo-region'] and (args['geo-boundaries'] == '1') then local qmaps=args['map-data-light'] or '' if qmaps ~='' and string.sub(qmaps,#qmaps) ~= ',' then qmaps=qmaps..',' end local geoRegion = args['geo-region'] -- or add base-page here local geoSubRegion = args['geo-sub-region'] or 'P150' if not mw.wikibase.isValidEntityId(geoSubRegion) then geoSubRegion = 'P150' end if mw.wikibase.isValidEntityId(geoRegion) then local ct=1 local qtemp while (mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct]) do -- debugmsg('table '..ct..' - '..mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id) qtemp = mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id or nil if qtemp then if mw.wikibase.entityExists(qtemp) then qmaps=qmaps..qtemp..',' -- see if there is a geocode boundary Qvalue to add else debugmsg(mw.addWarning('unable to add geocode value for '..mw.wikibase.getLabel..','..qtemp)) end end ct=ct+1 end end if qmaps and qmaps ~= '' then -- add back the created/expanded list of boundary Qvalues, and remove trailing comma args['map-data-light'] = string.sub(qmaps,1,#qmaps-1) end end -- Create the basemap using mapframe local mapframecontent=getmapframecontent(args,'basemap') table.insert(result, frame:extensionTag{ name ='mapframe', content=mapframecontent, args={width=tostring(maplist.width), height=tostring(maplist.height), zoom=tostring(maplist.zoom), longitude=tostring(maplist.lonbase), latitude=tostring(maplist.latbase), mapstyle=maplist.mapstyle, frameless=true } } ) --Add coverall box to block the unhelpful links from mapframe - which wouldn't include all the dots. Reinstate some links to osm and wikimedia table.insert(result,'<div style="position: absolute;width:'..tostring(maplist.width)..'px; height:'..tostring(maplist.height)..'px;') table.insert(result,'top:0px;left:0px;background-color:#FFFFFF00; color:inherit;"></div>') --Add replacent hover-links for OpenStreetMap and maps terms and conditions table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-13)..'px; width: 12px; height: 12px">') table.insert(result,'[[file:Transparent.svg|12px|link=https://www.openstreetmap.org/copyright|'..aboutOSM..']]</div>') table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-110)..'px;') table.insert(result,'width: 12px; height: 12px background-color: transparent; color:inherit;">') table.insert(result,'[[file:Transparent.svg|12px| link=https://foundation.wikimedia.org/wiki/Policy:Maps_Terms_of_Use|'..termsOfUse..']]</div>') -- Add scale-line if not args.scalemark or args.scalemark~='0' then local top=maplist.height-42 local left=maplist.width-61-(tonumber(args.scalemark or '1')) local minipos=string.lower(args.minimap or '') -- scalemark gets pushed left if it would be behind the minimap if minipos~='' and not(string.find(minipos,'left') or string.find(minipos,'top') ) then local offset=tonumber(args.scalemark or '1')-tonumber(args['mini-width'] or '60') if offset<1 then left=maplist.width-61-tonumber(args['mini-width'] or '60') end end if maplist.width-left >216 then top=top+14 end -- shunt scaleline down if it is beyond the copyright stuff local scalek,scalem=getScale(maplist.zoom, maplist.latbase, magVal) local magReduce='' table.insert(result,"<div style=\"display:inline-block; position: absolute; background-color: #111111") table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;M0,8 l0,4 l20,0 l0,-4 l-0.3,0 l0,3.7 l-19.4,0 l0,-3.7 z&#39;) ") table.insert(result,"; width:20px") -- path is a 20px20px box, and then rescales if magVal==1 then table.insert(result,"; top:"..tostring(top-1).."px") table.insert(result,"; left:"..tostring(left+16).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5)..", "..tostring(1.5)..")") else -- shrink the scalemark to compensate for magnification table.insert(result,"; top:"..tostring(top-(magVal*0.28)).."px") table.insert(result,"; left:"..tostring(left+(16*(magVal*1.15))).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5*(1/magVal))..", "..tostring(1.5*(1/magVal))..")") magReduce= 'scale('..tostring(1/magVal)..')' end table.insert(result,"\"></div>") table.insert(result,'<div style="position: absolute; top: '..tostring(top)..'px; left: '..tostring(left+47)..'px; font-size: 9.5px; line-height: 126%; width: fit-content;') table.insert(result,'color: #444433; background-color: transparent; text-align: right; transform: '..magReduce..' translateX(-100%);">'..scalek..'<br>'..scalem..'</div>') end --Set up the shapeList and dotList tables, to provide data to go on the map local sgNumbers,sgSortable={},{} --s1,s2 sgNumbers["1"]="1" if args.useFormatStyle and args.useFormatStyle=='shortstyle' then shapeList=ParseShapeTypes (shapeList,args,"1") for argindex,argv in pairs(args) do -- build a list of all the numbered sg's that have been used if string.find(argindex,"sg[a-f,n%d]+") == 1 then -- only look through the sga,sgb, sgc,sgd,sge,sgf and sgn args local x=string.match(argindex,"[%d]+") -- find its number if x and not sgNumbers[x] then sgNumbers[x]=x end -- only add if not already found end if string.find(argindex,"sg[a-f]H") == 1 and highlightNum then highlightOption=true end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form for k,v in pairs(sgSortable) do -- work through the sorted list, parsing each set of sg's in turn, from 1 upwards shapeList=ParseShapeTypes (shapeList,args,v) end if highlightOption==true then shapeList=ParseShapeTypes (shapeList,args,'H') end end local dotList,dotresult,dotItemTable,dotGroupList={},{},{},{} if (not args.useFormatStyle) or args.useFormatStyle=='standardstyle' then local dotmax=0 local qset={} for indx,val in pairs(args) do if string.match(indx,'mark%-coord[%d]+') or string.match(indx,'lat[%d]+') or string.match(indx,'geo%-data[%d]+') then dotmax=math.max(dotmax, tonumber(string.match(indx,"[%d]+"))) if string.match(indx,'geo%-data[%d]+') then local argnum=string.sub(indx,9,#indx) or '' if args['mark-title'..argnum] == 'none' or (string.find(args['mark-title'..argnum] or 'x','nolist')== 1) then -- debugmsg('sort values ignored '..argnum..', '..args['mark-title'..argnum]) else qset[val]=getQset (val, (args['geo-data-type'] or 'P1082'), argnum, args) end end end if (indx=='shapeH') or (indx=='shape-colorH') or (indx=='shape-outlineH') or (indx=='label-colorH') then highlightOption=true end end local qlist=args['geo-data-list'] if qlist then if string.sub(qlist,#qlist) ~= ',' then qlist=qlist..',' end else qlist='' end local geoRegion = args['geo-region'] -- or add base-page here local geoSubRegion = args['geo-sub-region'] or 'P150' if not mw.wikibase.isValidEntityId(geoSubRegion) then geoSubRegion = 'P150' end if (args['find-geo-dots'] or '1') ~= '0' and geoRegion and mw.wikibase.isValidEntityId(geoRegion) then local ct=1 local qtemp while (mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct]) do -- debugmsg('table '..ct..' - '..mw.wikibase.getBestStatements(adminRegion,adminSubRegion)[ct].mainsnak.datavalue.value.id) qtemp = mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id or nil if qtemp then qlist=qlist..qtemp..',' -- add subregion Qvalue to the list end ct=ct+1 end end if qlist and #qlist>1 then -- create a table from the csv list local _, c = qlist:gsub(",","") local qtab=splitItem(qlist,c) for xd,yd in pairs(qtab) do yd=string.upper(yd) if qset[yd] == nil and mw.wikibase.isValidEntityId(yd) then dotmax=dotmax+1 qset[yd]=getQset (yd, (args['geo-data-type'] or 'P1082'), tostring(dotmax), args) args['geo-data'..tostring(dotmax)] = yd -- debugmsg(dotmax..' = '..(string.upper(yd) or 'Q Not found')..': '..(mw.wikibase.getLabel(yd) or 'label Not found')..': '..(mw.wikibase.getSitelink(yd) or 'link Not found')) elseif not mw.wikibase.isValidEntityId(yd) then debugmsg(mw.addWarning(yd..' is not a [[wikidata]] page. geo-data-list needs comma-separated Qvalues' )) else debugmsg(mw.addWarning(yd..' is duplicate')) end end end -- qset contains set of Qvalues and can be from wikidata geo-region, list of geo-data-list Q values or individual geo-data1= etc local ts = {} -- table to hold the keys local useQset=false for ky, val in pairs(qset) do table.insert(ts, {key = ky, value = val[1], num=val[2]}) useQset=true end if useQset then local order = string.lower(args['geo-list-sort'] or 'a') -- sort the list from previously stored sortkey, either Lowest or Highest first if string.find(',al,vl,nl,wl,west,w,alpha,a,' , ','..order..',') then table.sort(ts, function (a,b) return lessthan(a.value, b.value) end) else table.sort(ts, function (a,b) return morethan(a.value, b.value) end) end for ky,val in ipairs(ts) do -- assign values from sorted list to numbered parameter args['numbered'..val.num] = ky -- The numbered parameter becomes the value for each dot that has a geo Qvalue, from manual, list or wiki sources. end end dotItemTable=tradstyleParseShapes(args,dotItemTable,dotmax) for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) or args['geo-data'..x] then sgNumbers[x]=x -- add it to the list end end else for indx in pairs(args) do if string.match(indx,'dot[%d]+') then table.insert(dotList,indx) end --add the index name for dot1, dot2 etc to dotList end table.sort(dotList,morethan) for indx,dotName in pairs(dotList) do dotresult=ParseData(args,string.match(dotName,'[%d]+') ) -- using each dot number, assign the settings for each dot to a dotresult item line table.insert(dotItemTable,dotresult) -- and store that item line within the dotItemTable end end for arcVal = 65,91 do -- check through args looking for any arcs local arcLetter=string.char(arcVal) if args['arc-text'..arcLetter] or args['arc'..arcLetter] then makeArcText(args,result,arcLetter) end end local dotdivs='' local ddots=0 if dotItemTable[1] then local ddots=(dotItemTable[1].lat or 0)+(dotItemTable[1].lon or 0) end local fgroup='F'..tostring(maplist.latbase+maplist.lonbase+ddots ) local FullscreenList={} local addcomma='' for i,dotitem in pairs(dotItemTable) do -- working throug each dot item, merge the dot and shape values into a full set of css text local dotgroup= dotitem.group or "0" if dotitem.posType=='mark-line' and dotitem.markDest then --find destination xy values for any mark-lines for n,v in pairs(dotItemTable) do if v.code == dotitem.markDest then dotitem.gridx2=v.gridx dotitem.gridy2=v.gridy break end end end local qtype=dotitem.group -- find which shape group each dot has been assigned --debugmsg('dotgroup='..qtype..', sg='..(sgNumbers[qtype] or 'nil')..' , shapeList='..shapeList[qtype].shape) if not sgNumbers[qtype] then qtype="0" end --shapeList[dotitem.group] will give access to the shape values for that dot if highlightNum==dotitem.code and highlightOption==true and shapeList['H'] then table.insert(result, getshapetable(dotitem,shapeList['H'])) else table.insert(result, getshapetable(dotitem,shapeList[qtype])) -- Add the actual css instructions for each dot end if shapeList[dotgroup] and not string.find((dotitem.param1 or ''),'nomap') then -- only add if not excluded with 'nomap' labelText local ftext='' if dotitem.dotTag~='' and not string.match(dotitem.labelText or '','[%d]') then ftext=stripdivs(dotitem.dotTag or '')..' <br>' end if (dotitem.labelText ~= ftext) and dotitem.dotLink =='' then ftext=ftext..' '..stripdivs(dotitem.labelText)..'<br>' end if (dotitem.dotLink) and (dotitem.dotLink ~='') then ftext=ftext..dotitem.dotLink..'<br>' end if dotitem.imageName then ftext=ftext..'[[File:'..dotitem.imageName..'|250px]]' end table.insert(FullscreenList,1, makeFullscreenItem (string.gsub(ftext,"[\n]+"," "), dotitem.info,round(dotitem.lat,5),round(dotitem.lon,5),fgroup,shapeList[dotgroup].shapeColor)..addcomma ) addcomma=', ' end -- makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) -- Always add to start of list, to reverse the sequence, and separate with commas except for first item, which is now at the end end if args.legendBox then makeLegendBox(result,args) end if args.minimap or args['mini-locator'] then makeLocatorMap(args,result) end -- add tag link and details for fullscreen version addcomma='' if (mapframecontent or '[]') ~= '[]' then addcomma=',' end mapframecontent=getmapframecontent(args,'fullscreen') local contentstart='[ '..mapframecontent..addcomma..'{ "type": "FeatureCollection", "features": [ ' --extra features after first square bracket local contentend=' ] } ]' table.insert(result, '<div style="position: absolute;top: 9px;left: '..tostring(maplist.width-34)..'px">') table.insert(result, '<div style="color: white; opacity:100; font-size: 19px; font-weight:normal; text-align: left;">') table.insert(result, frame:extensionTag{ name ='maplink', content=contentstart..table.concat(FullscreenList)..contentend, args={zoom=tostring(maplist.zoom+1), class='no-icon', frameless='1', latitude=tostring(maplist.latbase), longitude=tostring(maplist.lonbase), --add invisble 'en-spaces' for tooltip text='<div title="'..fullscreenlinktext..'">&nbsp;&nbsp;&nbsp;</div>'} } ) table.insert(result,'</div></div>') -- end of maplink ----- -- add closing div for main map table.insert(result,'</div>') -- collate caption material to go in the outer div class if captionTable ~= '' and (not args['auto-caption']) then args['auto-caption']='on' end --default to include list if using geo values local autocaption=string.lower(args['auto-caption'] or 'no') local autoOff=autocaption:match("(%w+)(.*)") -- select the first word in autocaption and see if it is a negativeAnswer) if negativeAnswer[autoOff] then captionTable='' end if args.caption or (not negativeAnswer[autoOff]) then table.insert(result,'<div class="thumbcaption" style="text-align:left">') if args.caption then table.insert(result,args.caption) if captionTable ~= '' then table.insert(result,'<hr>') end end end local columns=tonumber(autoOff:match("[%d]+") or '1') if columns>1 then columns=round(maplist.width/(columns*17), 0) end -- convert from em to px for historical reasons --for k in pairs(dotList) do capchk=capchk..(args["dotlink"..k] or '') end local capchk=nil local captionList = {} for key, value in pairs(dotItemTable) do -- only add an autocaption line if there is both a dotTag and a dotLink line available and it is not marked as nolist if value.dotTag and value.dotTag~='' and (not string.find(value.param1 or '','nolist')) and string.gsub(value.dotLink or '',"%s+","")~='' then table.insert(captionList, {key = key, value = value}) if captionTable == '' then capchk=true else -- add the next line of content to the html table capchk=false end end end if captionTable ~= '' then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) for k,v in pairs(captionList) do local nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag local ngrp=v.value.group if captionTableText[ngrp] then -- debugmsg('table text for'..v.value.dotTag..', '..captionTableText[v.value.group][1]..', '..captionTableText[v.value.group][2]..', '..captionTableText[v.value.group][3]..' ('..captionTableText[v.value.group][4]..')') local c1,c2 c1,c2=checkColors(shapeList[ngrp].shapeColor) local cDot='<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;' cDot=cDot..'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div>' captionTable = htmlTableLine(captionTable,cDot,captionTableText[ngrp][1], captionTableText[ngrp][2], captionTableText[ngrp][3], captionTableText[ngrp][4]) end end end if capchk and (not negativeAnswer[autoOff]) then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) -- local myDivision = string.gsub((args.toggletext or toggletext), "%s+", "") if string.find(autocaption,'collaps') then table.insert(result,'<div class = "wikitable mw-collapsible ') if string.find(autocaption,'collapsed') then table.insert(result,'mw-collapsed') end table.insert(result,'" style="width:100%; line-height: 17px; border-style:none; text-align: center"><b>'..(args.toggletext or toggletext)..'</b>') end if string.find(autocaption, 'columns=') then columns=string.match(autocaption,'[%d]+',string.find(autocaption, 'columns=') ) end table.insert(result,'<div class="mw-collapsible-content" style="column-count:'..columns..'; column-rule:solid 1px;text-align:left;padding-top:5px">') table.sort(dotList,lessthan) local nval,ngrp='','0' for k,v in pairs(captionList) do nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag ngrp=v.value.group or '0' if v.value.dotLink and v.value.dotLink~='' and nval and nval~='' then local c1,c2 if nval==args.highlight then c1,c2=checkColors(shapeList['H'].shapeColor) else c1,c2=checkColors(shapeList[ngrp].shapeColor) end table.insert(result,'<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;') table.insert(result,'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div> '..v.value.dotLink..'<br>') end end table.insert(result,'</div>') -- end for caption-content div if string.find(autocaption,'collaps') then table.insert(result,'</div>') end -- end for toggle frame end if captionTable ~= '' then table.insert(result,captionTable..'</table>') end if args.caption or (not negativeAnswer[autoOff]) then table.insert(result,'</div>') end -- end for whole caption frame table.insert(result,'</div></div>') -- outer two frames if args.float == 'center' or args.float=='centre' then table.insert(result,'</div>') end if args['show-new-format'] == 'hints' then -- provide a 'format hint panel' in the 'Preview Box' local w="<small>Below are some template hints for the 'sga' compressed version, " w=w..'{{tl|OSM Location dots}}. It can use these, instead of the more verbose {{tl|OSM Location map}} parameter format. ' w=w..'Data is divided between a "ShapeGroup" and the "Dots", so that a single shapeGroup can be used for multiple dots on the map. ' previewMsg(w..'(nb. the "group" value can be the number or an assigned name of a shapeGroup)</small>') w='<small>{{tl|OSM Location dots}}: | dot(n)=group,lat,lon,dotTag | ' w=w..'dotlink=link/tooltip | dotlabel=label,position,dx,dy,param1,info | dotpic=filename <br>' w=w.."(nb. param1 options include 'nolink' 'nolist' 'nomap' 'hemisphere-1' 'hemisphere+1', 'noline' - quotes not required, separate with spaces).<br>" w=w..'| sga = Shape,Sizepx,Color,Angledeg | sgb= OutlineWidth,Color,Style | sgc=TextSize,Color,Angle,bold italic <br>' w=w..'| sgd=TagSizepx,Color,Spacer,Angledeg | sge=LineWidth,Color,Style | sgf=TextSpacingpx,LineHeight%,Outlinepx,backgroundColor<br>' w=w..'| sgn=Name (optional, to assign a meaningful name) | sgp=Parent (can be the name or number of the parent shapeGroup. ' previewMsg(w..'Each shapegroup will inherit values from a parent, stretching back to "sga1" and its default values.)</small>') if #pmsg then local dbg={} for i,x in pairs(pmsg) do table.insert(dbg,x..'<br>') end dbg=mw.addWarning(table.concat(dbg)) table.insert(result, dbg) end end if args.coordtest then debugmsg(mw.text.nowiki(args.coordtest)) end for i,x in pairs(msg) do table.insert(result, x..'<br>') end return table.concat(result) end function p.main(frame) local args = getArgs(frame) local itemTab={} maplist.width=tonumber(args.width) or 400 maplist.height=tonumber(args.height) or 300 if args.coord then itemTab=splitItem(convertCoordsTrad (args.coord),2) maplist.latbase=itemTab[1] maplist.lonbase=itemTab[2] else maplist.lonbase=tonumber(args.lon) or 5 maplist.latbase=tonumber(args.lat) or 0 end maplist.zoom=tonumber(args.zoom) or 1 visibleLinks=args.showlinks highlightNum=args.highlight if args.nolabels=='1' then maplist.mapstyle='osm' else maplist.mapstyle='osm-intl' end if maplist.width >= 400 and not(args['show-q-values']) then args['show-q-values'] = '1' -- default to on if wider than 400px elseif maplist.width < 320 then args['show-q-values'] = '0' end -- turn off Qvalus if frame is too narrow, even if requested return p._main(args) end return p ie9mb61diifnhnf838u73j21yrzoo0m 204451 204450 2026-07-05T14:40:26Z HaxelKaszëba 17492 204451 Scribunto text/plain require('strict') local delink=require('Module:Delink').delink local getArgs = require('Module:Arguments').getArgs local p = {} local maplist={} local sgNames={} local highlightOption=false local highlightNum local visibleLinks local geoTotalPop = 0 local captionTable = '' local captionTableText = {} -- This module creates framed maps of anywhere in the world, at the required scale, and enables annotations, -- dots, shapes, lines and other ways to customise the area of the map being shown. It also provides a link -- to an interactive fullscreen version, which has locator dots instead of annotations and shapes. -- This is the 2025 successor module to a wiki-markup template version of 2024, which itself was a successor -- to the 'Graph'/VEGA driven template that was begun in 2016, until the Vega version was switched off in 2023. -- This module is called from template {{OSM Location map}}, which uses the same parameter formats as before. -- In addition it may at some point be possible to use a more concise parameter format using the template {{OSM Location dots}} -- In general the css output from the two formats will be identical, but the the concise version will allow bits of -- greater control over some of the settings. -- see the documentation on the two template pages for details of how to use the mapping features. -- If language customisation is needed, there are text items below that can be translated. Also see the color table -- below with details of how to add additional color names to allow localised alternatives. -- (Translating other language shape-types could be possible, but has not currently been contemplated. -- Parameter name translation would be harder but likely to be possible, ideally still retaining compatibility -- with template calls already written using English). -- -------------- the following text variables are used within the template, and can be translated as required --------------- -- -------------- you may wish to retain the English version as a comment, to keep track of the original phrase -------------- local negativeAnswer={no=1,'0'-1,off=1} local fullscreenlinktext = 'Click for interactive fullscreen map with links to nearby articles' local toggletext = 'List of numbered map items:' local termsOfUse = 'Maps: terms of use' local aboutOSM = 'About OpenStreetMaps' local yards = 'yds' local miles = 'miles' local metres = 'm' local km = 'km' local sqkm = 'sq.km' local year = 'year' local notFound = 'not found' local totalItems = 'Total' local sumOfDatapoints = 'sum of datapoints' local numberCol = 'No.' -- abbreviated for top of column of code numbers local wikidataLink = 'Wikidata page' local shapeList={} --This sets up the 'factoryDefault' shape group 0 shapeList["0"]={shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border of the shape outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items text settings for labels textSZ="11px", textCL="darkgrey", textNG="0deg", --sgf further text settings textSP="0px", textLH="120%", textOL="0px", textBG="transparent", --sgd items for dotTag text settings tagSize="10px", tagColor="white", tagSpacer="0px", tagAngle="0deg", --sge items for extension line to connect label to dot textEW="0px", textEC="darkgrey", textES="solid" } local colorList={} -- used by colorLookup to catch unsupported colors (eg 'LimeGreen'), to convert to generic version colorList['green']='hardgreen' -- it could also be added to to include alternative language equivelants, for a quick solution. colorList['red']='hardred' -- colorList ['source'] = target colorList['white']='white' -- converts any color that includes 'source' into its equivelent target colorList['blue']='hardblue' -- note, for translation you can add to this list, rather than replace it, colorList['brown']='brown' -- which would mean existing map definitions in english would also still work, alongside translated ones colorList['grey']='hardgrey' colorList['gray']='hardgrey' colorList['purple']='hardpurple' colorList['orange']='hardorange' colorList['leaf']='hardleaf' --for a more thorough translation, you can add all the variants of the colors as further CTB elements and hex values or redirects local CTB={} -- set up a table of color names (the CTB Color table index) and html hash colorhex values. CTB["paleblue"],CTB["softblue"],CTB["hardblue"],CTB["darkblue"]="#D6E1EC","#77A1CB","#4B77D6","#1c559e" CTB["palered"],CTB["softred"],CTB["hardred"],CTB["darkred"] = "#FCC6C0","#EC644B","#DB3123","#AA1205" CTB["palegreen"],CTB["softgreen"],CTB["hardgreen"],CTB["darkgreen"]= "#D2F0E5","#81AF81","#269F46","#0b7527" CTB["paleleaf"],CTB["softleaf"],CTB["hardleaf"],CTB["darkleaf"]= "#dff5c1","#b5e376","#8cc244","#679c21" CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"]= "#E8E8D6","#AAAA88","#777755","#444433" CTB["palegray"],CTB["softgray"],CTB["hardgray"],CTB["darkgray"]=CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"] CTB["palebrown"],CTB["softbrown"],CTB["hardbrown"],CTB["darkbrown"]="#FAF6ED","#CCB56C","#AD7F14","#754910" CTB["palepurple"],CTB["softpurple"],CTB["hardpurple"],CTB["darkpurple"]="#e0d1e6","#c784e0","#a029cf","#7a05a8" CTB["paleorange"],CTB["softorange"],CTB["hardorange"],CTB["darkorange"]="#ffedc2","#ffcf61","#EEB533","#e39f05" CTB["black"],CTB["white"],CTB["yellow"]="#000000","#FFFFFF","#FAF039" CTB["background"],CTB["paleground"],CTB["beigeground"]="#f9f6f2","#FEFEFA","#F5F5DC" CTB["beige"]=CTB["beigeground"] CTB["aqua"],CTB["teal"],CTB["fuchsia"] = "#00FFFF","#008080","#FF00FF" CTB["maroon"],CTB["olive"],CTB["navy"] = "#800000","#808000","#000080" CTB["lime"],CTB["limegreen"],CTB["aquamarine"] = "#00FF00","#32CD32","#7FFFD4" CTB["silver"],CTB["yellow"],CTB["orchid"] = "#800000","#FFFF00","#DA70D6" -- set up a table of predefined clip-paths local pathshape={} pathshape.squaredd = "M 19,1.25 l 0,18 -18,0 0,-18 18,0m-1,1 -16,0 0,16 16,0 0,-16m-1,1 0,14 -14,0 0,-14 14,0zm-1,1 -12,0 0,12 12,0 0,-12zm-1,1 0,10 -10,0 0,-10 10,0z" pathshape.squared = "M 18,2.5 l 0,15 -15,0 0,-15 15,0m-1,1 -13,0 0,13 13,0 0,-13zm-1,1 0,11 -11,0 0,-11 11,0z" pathshape.triangledd="M 0 20,20 20,10 0,0 20ZM1.5 19,10 1.7,18.5 19,1.5 19ZM3 18,17 18,10 3.8,3 18ZM4.5 17,10 5.4,15.4 17, 4.5,17ZM6 16,13.8 16,10 7.4z" pathshape.triangled ="M1,18 l 18,0 l -9,-18 l -9,18zm1.7,-1.1 l 7.3,-14.6 l 7.3,14.6 l -14.6, 0zm1.7,-1 l 11.0,0 l -5.5,-11 l -5.5,11z" pathshape.circledd = "M0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm0.8,0a9.2,9.2 0 1,1 18.4,0a9.2,9.2 0 1,1 -18.4,0m1,0a8.2,8.2 0 1,0 16.4,0a8.2,8.2 0 1,0 -16.4,0zm0.8,0a7.2,7.2 0 1,1 14.8,0a7.2,7.2 0 1,1 -14.8,0m1,0 a6.4,6.4 0 1,1 12.8,0a6.4,6.4 0 1,1 -12.8,0z" pathshape.circled = "M2.5,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0m0.8,0 a5,5 0 1,1 11.4,0a5,5 0 1,1 -11.4,0" pathshape.diamond = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10z" pathshape.diamondd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm1,0 l 6,8.5 l 6,-8.5 -6,-8.5 -6,8.5zm1,0 l 5,-7 5,7 -5,7 -5,-7z" pathshape.diamonddd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm0.75,0 l 6.25,9 l 6.25,-9 -6.25,-9 -6.25,9zm0.75,0 l 5.5,-8 5.5,8 -5.5,8 -5.5,-8zm0.75,0 l 4.75,7 l 4.75,-7 -4.75,-7 -4.75,7zm0.75,0 l 4,-6 4,6 -4,6 -4,-6z" pathshape.crossd = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0zM2.3,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.cross = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0z" pathshape.thincross = "M2,12 l6,0 l0,6 l4,0 l0,-6 l6,0 l0,-4 l-6,0 l0,-6 l-4,0 l0,6 l-6,0z" pathshape.fivepointstar = "M10 0 L12.245 6.91 19.511 6.91 13.633 11.18 15.878 18.09 10 13.82 4.122 18.09 6.367 11.18 0.489 6.91 7.755 6.91Z" pathshape.fivepointstard = "M10 1.5 L 11.90825 7.3735 18.08435 7.3735 13.08805 11.003 14.9963 16.8765 10 13.247 5.0037 16.8765 6.91195 11.003 1.91565 7.3735 8.09175 7.3735 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sixpointstar = "M10 0 L12.323 5.977 18.66 5 14.645 10 18.66 15 12.323 14.023 10 20 7.677 14.023 1.34 15 5.355 10 1.34 5 7.677 5.977Z" pathshape.sixpointstard = "M10 1.5 L 11.97455 6.58045 17.361 5.75 13.94825 10 17.361 14.25 11.97455 13.41955 10 18.5 8.02545 13.41955 2.639 14.25 6.05175 10 2.639 5.75 8.02545 6.58045 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sevenpointstar = "M10 0 L12.048 5.747 17.818 3.765 14.602 8.95 19.749 12.225 13.69 12.943 14.339 19.01 10 14.72 5.661 19.01 6.31 12.943 0.251 12.225 5.398 8.95 2.182 3.765 7.952 5.747Z" pathshape.sevenpointstard = "M10 1.5 L11.7408 6.38495 16.6453 4.70025 13.9117 9.1075 18.28665 11.89125 13.1365 12.50155 13.68815 17.6585 10 14.012 6.31185 17.6585 6.8635 12.50155 1.71335 11.89125 6.0883 9.1075 3.3547 4.70025 8.2592 6.38495 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.eightpointstar = "M10 0 L11.88 5.46 17.071 2.929 14.54 8.12 20 10 14.54 11.88 17.071 17.071 11.88 14.54 10 20 8.12 14.54 2.929 17.071 5.46 11.88 0 10 5.46 8.12 2.929 2.929 8.12 5.46Z" pathshape.eightpointstard = "M10 0 L10 1.5 L11.598 6.141 16.01035 3.98965 13.859 8.402 18.5 10 13.859 11.598 16.01035 16.01035 11.598 13.859 10 18.5 8.402 13.859 3.98965 16.01035 6.141 11.598 1.5 10 6.141 8.402 3.98965 3.98965 8.402 6.141ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.ring="M2.6,9.5a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.semicircle = "M0,5 C0,18.3,20,18.3,20,5 L 0,5z" pathshape.arrowhead = "M 10,10.2 l 10,2.8 -2,-3 2,-3 -10,2.8 -0.2,0.2z" pathshape.boxd=pathshape.squared pathshape.boxdd=pathshape.squaredd pathshape.ellipsed=pathshape.circled pathshape.ellipsedd=pathshape.circledd local msg={} local function debugmsg(txt) table.insert(msg,txt) end local pmsg={} local function previewMsg(txt) table.insert(pmsg,txt) end local function colorLookup(color) for c,l in pairs(colorList) do if string.find(color,c) then return l end end return color end local function getColor (color,chk) local c local opacity="100" if not color or color=='' then color='hardgrey' end if color=="transparent" then return color end if color=="background1" then color='background' end if string.byte(color,1,1)==35 and (#color == 7 or #color == 9) then c=color elseif string.byte(color,1,1)==35 and #color == 4 then c=string.sub(color,1,2)..'f'..string.sub(color,3,3)..'f'..string.sub(color,4,4)..'f' else local s=color..'1' s= s:sub(0,s:find("%d")-1) opacity=string.match(color,"%d+") if not CTB[s] then s= colorList[s] end -- check for synonyms and translations if not CTB[s] then debugmsg(mw.addWarning('color = '..color..'. The color name is not defined. Used default grey instead')) end c=CTB[s] or CTB.hardgrey end if opacity and (tonumber(opacity) < 100) and string.find(c,"#")==1 and string.len(c)==7 and opacity~="" then local hexval=string.format("%x",(math.floor(tonumber(opacity)*2.55))) c=c..hexval end return c end function p.colorvalue(frame) -- enable external access to the CTB colorTable values. usage: {{#invoke:OSM Location map|colorvalue|color=hard blue}} local c if not frame.args.color or frame.args.color=='' then c='grey' else c=string.lower(string.gsub(frame.args.color,'%s+','')) end return string.upper(string.sub(getColor(c),2)) end local function checkColors(color) local c=getColor(color,'check') local opacity =1 -- calculate colour brightness and return black or white for contrast if c=='transparent' then return c,'#000000',0 end if not (string.find(c,'#')==1) then return c,'#FFFFFF',0 end if #c>8 then opacity= tonumber('0x'..(string.sub(c,8,9)))/500 end local r=tonumber('0x'..(string.sub(c,2,3)))/255 local g=tonumber('0x'..(string.sub(c,4,5)))/255 local b=tonumber('0x'..(string.sub(c,6,7)))/255 if 0.2126 * r + 0.7152 * g + 0.0722 * b / opacity < 0.7 then return c,'#FFFFFF',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity else return c,'#000000',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity end end local function morethan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) and tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%-?%f[%d]%d[,.%d]*%f[%D]') ) b = tonumber(string.match(b, '%-?%f[%d]%d[,.%d]*%f[%D]') or '0') end return a>b end local function lessthan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) and tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%-?%f[%d]%d[,.%d]*%f[%D]')) b = tonumber(string.match(b, '%-?%f[%d]%d[,.%d]*%f[%D]') or '0') end return a<b end local function getsize(size) --size1 is between 1 and 3 values, each with px, equating to width,height,corner-rounding --eg '15px 25px 5px' (spaces are optional) or '18px'. returns three numbers local sizeval = {} for v in string.gmatch(size, "[^px]+") do table.insert(sizeval,v) end sizeval[1] = tonumber(sizeval[1]) or 13 sizeval[2] = tonumber(sizeval[2]) or sizeval[1] sizeval[3] = tonumber(sizeval[3]) or 0 return sizeval[1],sizeval[2],sizeval[3] end local function coord2text(coord) -- looks through the output from {{coord}} to find the lat and long decimal values -- and converts compass points to minus or not-minus, return with separating comma. local lat = string.match(coord,'[%.%d]+°[NS]') local lon = string.match(coord,'[%.%d]+°[EW]') local neg={N="",S="-",W="-",E=""} return neg[string.match(lat, '[NS]')]..string.match(lat,'[%.%d]+')..","..neg[string.match(lon, '[EW]')]..string.match(lon,'[%.%d]+') end local function convertCoordsTrad (row) local coords='' if row and string.find (row,'<span class="geo">') then local a,b=string.find(row,'<span class="geo">') local start=b+1 a,b=string.find(row,"</span>",b) local finish=a-1 coords=string.sub(row,start,finish) coords=string.gsub(coords,'; ',',') end return coords end local function convertCoords (row) local start,finish,lat,lon,coords,says if row then local a,b=string.find(row,"<span class=") start=a while a do -- find the final span> finish=b a,b=string.find(row,"span>",b) end if start then coords= string.sub(row,start,finish) says="" if string.find(coords,'<span class="error">') then error("coord error: badly formed coordinates",0) end coords=coord2text(coords) coords = string.sub(row,1,start-1)..coords..string.sub(row,finish+1) else coords=row end return coords else return "Nothing to see here" end end local function fillCommas(val,max) local line='' if not val then line=',' -- ensure there is some content else line = val --string.lower(string.gsub(val,"%s+","")) -- or strip spaces end if string.find(line,',') == 1 then line=' '..line end -- ensure initial comma is not skipped local _, count=string.gsub(line,",","") -- add enough subsequent commas for all entries line=line..string.rep(',',max-count) while(string.find(line,",,") ) do line=string.gsub(line,",,",", ,") --ensure string.gmatch doesn't ignore any empty items by padding with spaces end return line end local function makeLinkBox(left,top,wid,label, link) local linkBoxName='Transparent square.svg' if visibleLinks or '' =='yes' then linkBoxName='Red hollow square.svg' end local builder = mw.html.create('div') --display:inline-block; local t= string.find(label,'wikidata') -- exclude wikidata from tooltip line if t then label=string.sub(label,1,t-2) -- don't show wikidata value in tooltip if string.find(link,'wikidata') then link = '' end -- don't link on click to wikidata (if there was not another link) end builder :cssText('position:absolute;left:'..tostring(left-1-wid/2)..'px;top:'..tostring(top-1 + math.min(wid/2-12,0) - wid/2)..'px') :wikitext(string.format( '[[File:%s|%dpx|link=%s|%s]]', linkBoxName, wid+2, link, label )) return tostring(builder) end local function extractItem(row,searchItem) -- remove text following a searchItem or start of line, which might be in quote-marks to allow commas local xend,xstart=1,0 if not row then return '','' end if searchItem then xend,xstart= string.find(row or '',searchItem or 'image:') end if not xstart then return string.gsub(string.gsub(row or '',"%b\"\"", ''),"%b\'\'", '') or '','' end while row:byte(xstart+1) == 32 and xstart<#row do -- skip over any leading spaces xstart=xstart+1 end local xbyte=row:byte(xstart+1) if xbyte == 34 or xbyte == 39 then -- are they wrapped in single or double quotes xstart=xstart+1 xend=row:find(string.char(xbyte),xstart+1) else xend = row:find(',',xstart+1) -- if no quotes, we assume no commas if not xend then xend=#row+1 end end -- return residual row and extracted text return row:sub(0,xstart)..row:sub(xend), row:sub(xstart+1,xend-1) end local function itemCheck(item,ext) if not item then return nil end if not ext then ext='' end return (string.match(item,"[%.%-?%d]+") or '0')..ext end local function stripdivs(line) return string.gsub(line or '',"%b<>", ' ') end local function splitItem(item,max) -- takes a commas-sep list and returns a table of lowercase items with no spaces, or nil local r={} local x=1 item=string.lower(fillCommas(item,max)) for t in string.gmatch(item,"[^,]+") do r[x]=string.gsub(t,"%s+","") if r[x]=='' then r[x]= nil else -- residual items might have commas if x>max then r[max]=(r[max] or '')..', '..r[x] end end x=x+1 end return r end local function ParseShapeTypes (result,args,sgval) -- for use with compressed, comma-separated 'sg plus dots' parameters --[[ shape table items and default values as set at top of page shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items label text textSZ="11px", textCL="white", textNG="0deg", textAT=attributes ("bold" and/or "italic") --sgd items for dotTag tagSize="11px", tagColor="darkgrey", tagSpacer="0px", tagAngle="0deg", --sge extension line textEW="1px", textEC="darkblue", textES="solid" --sgf fx for text labels textSP="0px" textLH="100%" textOL="1px", textBG="paleground", <!--| sga = shape,Size,Color,Angle|sgb= outlineWidth,color,style eg: sga1=circle,14px,blue,0deg| sgb1=0px,dark grey,solid | sgc=textSize,color,angle,bold/italic | sgd=tagSize,Color,Spacer,Angle eg: sgc1=11px,dark grey,0deg,normal| sgd1=9px,white,0px,0deg | sge=lineWidth,color,style |sgf=textspacing,lineHeight%,outlinepx,backgroundcolor, [bold,italic] eg: sge1=0px,black, solid| sgf1=0px,120%,0px,background | dot=shape-group/lat/lon/title/dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> --]] if args["sgn"..sgval] then local sgname=string.match(args["sgn"..sgval],"(%w+)(.*)") sgNames[sgname]=sgval end local parent= args["sgp"..sgval] if parent then parent=string.match(parent,"(%w+)(.*)") local pos= string.find(parent,"%d+") if pos == 1 then parent=string.match(parent,"%d+") else parent=sgNames[parent] or '1' end end if sgval~='H' then if not parent or tonumber(parent) > tonumber(sgval) then if sgval=="1" then parent="0" else parent="1" end end end local itemTab, line, filename result[sgval]={} result[sgval].shapeType=sgval line,filename=extractItem(args['sga'..sgval] or '','image:') if sgval=='1' and not args.sga1 then line='circle,12px,blue,0deg' end -- ensure there is a parent=1 sga result[sgval].shapeFile=filename or '' -- sga= Attributes for shape itemTab=splitItem(line,4) result[sgval].shape = itemTab[1] or result[parent].shape result[sgval].shapeSize=itemTab[2] or result[parent].shapeSize result[sgval].shapeColor=itemTab[3] or result[parent].shapeColor result[sgval].shapeAngle=itemCheck(itemTab[4],'deg') or result[parent].shapeAngle -- sgb= Border outline attributes for shape itemTab=splitItem(args['sgb'..sgval],3) result[sgval].outlineWidth=itemCheck(itemTab[1],'px') or result[parent].outlineWidth result[sgval].outlineColor=itemTab[2] or result[parent].outlineColor result[sgval].outlineStyle=itemTab[3] or result[parent].outlineStyle --sgc=character attributes for label itemTab=splitItem(args['sgc'..sgval],4) result[sgval].textSZ=itemCheck(itemTab[1],'px') or result[parent].textSZ -- size of text in px result[sgval].textCL=itemTab[2] or result[parent].textCL -- colour for text result[sgval].textNG=itemCheck(itemTab[3],'deg') or result[parent].textNG -- Angle for text result[sgval].textAT=itemTab[4] or result[parent].textAT -- attributes bold, and/or italic --sgd=dotTag attributes itemTab=splitItem(args['sgd'..sgval],4) result[sgval].tagSize=itemCheck(itemTab[1],'px') or result[parent].tagSize result[sgval].tagColor=itemTab[2] or result[parent].tagColor result[sgval].tagSpacer=itemCheck(itemTab[3],'px') or result[parent].tagSpacer result[sgval].tagAngle=itemCheck(itemTab[4],'deg') or result[parent].tagAngle --sge= extension line attributes itemTab=splitItem(args['sge'..sgval],4) result[sgval].textEW=itemCheck(itemTab[1],'px') or result[parent].textEW -- width result[sgval].textEC=itemTab[2] or result[parent].textEC -- colour result[sgval].textES=itemTab[3] or result[parent].textES -- style --sgf= fx for label text itemTab=splitItem(args['sgf'..sgval],4) result[sgval].textSP=itemCheck(itemTab[1],'px') or result[parent].textSP -- spacing value for letters result[sgval].textLH=itemCheck(itemTab[2],'%') or result[parent].textLH -- Angle for text result[sgval].textOL=itemCheck(itemTab[3],'px') or result[parent].textOL -- width of text-border line result[sgval].textBG=itemTab[4] or result[parent].textBG -- color for text background return result end local function round(x,dec) -- x=number [, dec=integer] returns numeric value with upto dec decimals (all but first trailing zeros get truncated) if (dec or 0)==0 then return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5) --this avoids .0 where dec=0 end dec =10^(dec) return x>=0 and math.floor(x*dec+0.5)/dec or math.ceil(x*dec-0.5)/dec end local function maptogrid(t,r,test) --[[ converts mercator projection longitude and latitude coordinates to x and y pixel coordinates, within a frame of given size, centre coordinates and zoom level. t is a table of named indices: {lon, lat, lonbase, latbase, width, height, zoom} output is two values, x and y, rounded to r decimal places--]] local x,y x=t.width/2 + ( ((math.rad(t.lon)*6378137) - (math.rad(t.lonbase)* 6378137) ) / (156543.03*math.cos(t.latbase/180)/(2^t.zoom) ) )*(1-(0.075*(math.abs(t.latbase)/90))) y=t.height/2 + ( ( (math.log(math.tan(math.rad(t.latbase)/2+math.pi/4))*6378137) - (math.log(math.tan(math.rad(t.lat)/2+math.pi/4))*6378137) ) / (156543.03*math.cos(t.latbase/180) / (2^t.zoom) ) )*(1-(0.075*(math.abs(t.latbase)/90))) return round(x,r),round(y,r) --source: python code at https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames and https://wiki.openstreetmap.org/wiki/Mercator --[[ width and height are the size, in pixels, of the map, which will be centerd around lonbase,latbase. Method: Convert lon and lonbase to meter-offsets from coord(0,0), and subtract lonbase from lon, zoom and latbase are used to scale the resulting meter-offset to pixels, and add it to width/2. Convert lat and lat-base to meter-offsets from coord(0,0), subtract lat from latbase, scale the resulting meter-offset to pixels, add it to height/2. A correction factor '*(1-(0.075*(math.abs(t.latbase)/90) ) )' compensates for an error that seems to creep in towards the edges of the map at higher latitudes. It was identified experimentally, and ensures a dot at the edge is in the same place as if that location is positioned at the centre. Original Python code for lat,lon to x,y where 0,0 is the centre of the map print('x=',width+(((math.radians(lon1) * 6378137)- (math.radians(lonbase) * 6378137))/ (156543.03*math.cos(latbase/180)/(2**zoom))),' y=',height+(( (math.log(math.tan(math.pi / 4 + math.radians(latbase) / 2)) * 6378137)- (math.log(math.tan(math.pi / 4 + math.radians(lat1) / 2)) * 6378137))/ (156543.03*math.cos(latbase/180)/(2**zoom))) ) --]] end local function getScale(zoom, lat, magVal) zoom = tonumber(zoom) or 0 lat = tonumber(lat) if not lat then lat = 0 end if magVal then magVal = tonumber(magVal) if magVal and magVal > 1 and magVal <= 2 then zoom = zoom + (magVal - 1) end end local dist = (156543.03 * math.cos(math.rad(math.abs(lat))) / (2 ^ zoom)) / 17 local y if dist < 1 then y = round(dist * 10, 1) return tostring(y * 100) .. metres, tostring(round(y * 109, 0)) .. yards elseif dist < 18 then y = round(dist, 0) return tostring(y) .. km, tostring(round(y * 0.621371, 1)) .. miles elseif dist < 500 then y = round(dist / 10) return tostring(y * 10) .. km, tostring(round(y * 6.21371, 0)) .. miles else y = round(dist / 100) return tostring(y * 100) .. km, tostring(round(y * 62.1371, 0)) .. miles end end local function ParseData (args,dotval) -- for use with compressed, comma-separated 'sg plus dots' parameters -- takes a structured series of comma-separated items which get parsed as the following: -- dot(n)= (sgNumber or Name),{{coord}} or (lat,lon), (dotTag) -- dotlink(n) = single-parameter text to give wikilink and/or title used by tootlip, fullscreen dots and autocaption list -- dotlabel(n) = 'label text',pos(left,roght,top,bottom,centre,auto),(dx), (dy) pixel offsets, params, info -- dotpic(n) = single parameter wikimedia filename for an image to use in photopanel and/or fullscreen dots -- dotfeature(n)= 'mark-line' (,linewidth,style,gap) or 'photo-panel' (,image-dim,panel-width,panel-height), draws line to n-1 -- label is used if either a position and/or an x,y offset are not 0,0 ( if no label then dotTag will be put at at the x,y offset, or over the dot -- label text can be autoaligned if x,y puts it left or right of the dot, or centered if above/below) -- quote marks are not needed unless including commas within the label text -- param1 is optional items separated by spaces, and can include [nolabel nolist nomap hemisphere+1 hemisphere-1] -- info is free wikitext, to be used in the fullscreen dot box. (use dotpic to show a picture) --<!--| dotx=shape-group,[lat,lon or {{coord}} ], dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> local result={} local count=1 local row = convertCoords (args["dot"..dotval]) -- swap in any {{coord}} values so they are csv lat and lon row=fillCommas(row,4) result.code=dotval -- store the parameter name as the id code for item in string.gmatch(row,"[^,]+") do -- iterate through 'row', adding each csv item in turn, if present if count==1 then --see if it is a number or a name local pos= string.find(item,"%d+") if pos == 1 then result.group=string.lower(string.gsub(item,"%s+","")) else item=string.match(item,"(%w+)(.*)") -- ensure just a single word result.group=sgNames[item] end elseif count==2 then result.lat=tonumber(string.match(item,"[%.%-?%d]+")) or 0-- find the number, with no non-numeric stuff elseif count==3 then result.lon=tonumber(string.match(item,"[%.%-?%d]+")) or 0 elseif count==4 then result.dotTag=item:match( "^%s*(.-)%s*$" ) or "" -- dotTag allows for internal spaces, but no commas end count=count+1 end row, result.labelText= extractItem(args["dotlabel"..dotval]) result.labelText= string.gsub(result.labelText,"[%^]+","<br>") -- convert hats to line breaks local item=splitItem(row,6) result.labelPos=item[2] or 'center' result.dx=tonumber(string.match(item[3] or '0',"[%.%-?%d]+")) or 0 result.dy=tonumber(string.match(item[4] or '0',"[%.%-?%d]+")) or 0 result.param1=string.lower(item[5] or '') if string.find(result.param1,"hemisphere-1",1,true) then result.lon=result.lon-360 elseif string.find(result.param1,"hemisphere+1",1,true) then result.lon=result.lon+360 end local txt = '' if item[6] then -- ensure all info elements are included, including commas count=1 local max=6 for t1 in string.gmatch(fillCommas(row,max),"[^,]+") do if count>max then txt=txt..',' end if count>=max then txt=txt..t1 end count=count+1 end end result.info=txt result.imageName = args['dotpic'..dotval] -- Get first wikilinked item (if any) from the args.dotlink and set this plus the delinked text local testx=args["dotlink"..dotval] or '' result.dotLink=testx if testx ~= '' then testx=stripdivs(testx) result.title=delink({ testx }) local linkstart= string.find(testx,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then result.dlink=delink( { string.sub(testx,linkstart,string.find(testx,']]',1,true)+1),wikilinks='target' } ) else result.dlink='' end else result.dlink='' result.title='' end if args['dotfeature'..dotval] then local item=splitItem(args['dotfeature'..dotval],6) if (item[1] or '') =='photo-panel' then result.ppwidth= tonumber(string.match((item[3] or '110'),"%d+")) -- panel width result.ppheight= tonumber(string.match((item[4] or '48'),"%d+") ) --panel height result.photowidth=round(tonumber(string.match((item[2] or '1.3'),"[%.%-?%d]+")) * result.ppheight+1,0) -- dimension to set image size result.photoImage=result.imageName result.posType='photo-panel' elseif (item[1] or '') == 'mark-line' then local x=tonumber(dotval or '0') result.markDest=item[5] or tostring(x-1) result.mlWidth= tonumber(string.match((item[2]) or '',"%d+") or '1') result.mlStyle= item[3] or 'solid' result.mlGap=tonumber(string.match((item[4] or ''),"[%d]+") or '0') result.posType='mark-line' end --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end maplist.lon=result.lon maplist.lat=result.lat result.gridx, result.gridy = maptogrid(maplist,1) -- convert geo coords to grid xy - using values from maplist table return result end local function multiCheck (args, argName, argVal, defVal, alt) if not alt then alt='nonexistant' end if argVal=='H' and not args[argName..'H'] then argVal=highlightNum or '1' end if argVal=='' then return (args[argName] or args[alt] or (args[argName..'D']) or args[alt..'D'] or defVal) -- unnumbered args do not inherit from D or 1 else return (args[argName..argVal]) or (args[alt..argVal]) or (args[argName..'D']) or (args[alt..'D']) or (args[argName..'1']) or (args[alt..'1']) or defVal end end local function htmlTableLine (htmlTable,h1,h2,h3,h4,h5,type) if type == 'header' then if (h4 or '') ~= 'no-year' then h4='<th class="unsortable">'..h4 or ''..'</th>' else h4 = '' end if (h5 or '') ~= '' then h5='<th class="unsortable">'..h5..'</th>' else h5 = '' end return htmlTable..'<tr><th>'..(h1 or '')..'</th><th>'..(h2 or '')..'</th><th>'..(h3 or '')..'</th>'..h4..h5..'</tr>' else if (h4 or '') ~= 'no-year' then h4='<td>'..h4 or ''..'</td>' else h4 = '' end if (h5 or '') ~= '' then h5='<td>'..h5..'</td>' else h5 = '' end return htmlTable..'<tr><td>'..(h1 or '')..'</td><td>'..(h2 or '')..'</td><td>'..(h3 or '')..'</td>'..h4..h5..'</tr>' end end local function formatInt(number) if number == 0 then return notFound end local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)') int = int:reverse():gsub("(%d%d%d)", "%1,") return minus .. int:reverse():gsub("^,", "") .. fraction end local function convertToSqKm (value,punit) if string.find(punit, 'Q35852') then value = value / 100 -- convert to sq km if it's hectares elseif string.find(punit, 'Q232291') then value = value /(1/2.59) -- or this if it's square miles elseif string.find(punit, 'Q81292') then value = value / 247.1 -- or this if it's acres end return value end local function assignTradstyleShape(shapeResult,default,dotResult,args,nval) local item,itemTab local autoDotTag='' local shapeWidth,shapeHeight=0,0 local argval=nval -- to catch the unnumbered shape series local qvalue='' if argval=='0' then argval='' end if nval=='H' then shapeResult.H={} end -- Assign autovalues if a wikidata Qvalue has been supplied -- some preliminary documentation -- geo-data-type= default is P1082 (population) -- other options might include number of households (P1538) and per capita income (P10622) or land area (P2046) -- geo-data-list= this is a comma-separated list of Q values, for a manually generated list that doesn't require nymbered parameters -- geo-scalefactor = can enlarge or shrink all the dots in proportion, depending an what the map needs -- geo-region = needs to be a Qvalue of the larger administrative region that the settlements/sub-regions are in -- geo-sub-region needs to be a Pvalue, but will default to P150 (some pages use 'has parts' = P527) -- geo-data3= etc numbered Qvalue item, if used in conjunction with other shape/label parameters -- shapeD= sets default shape, which migh be n-circle to include numbers. -- geo-number-size= will give a fixed size for shape numbers. (defaults to 12 when using proportional dots) -- geo-boundaries= if set to 1 will add any Qvalues for the sub-regions to 'map-data-light'. -- show-geo-dots= to use proportional dots. Will be fixed size if 0. (set mark-sizeD=0 to have no dot at all, eg to show labels or boundaries) -- find-geo-dots=1 or 0 (needs to also include geo-region) -- label-posD = gets additional options: [off] gives no label, [on] will position lower centre, -- [on+] includes numeric values beside/below label, [on^] will always put it below. [on-] always puts values beside if args['geo-data'..argval] then -- and (args['mark-title'..argval] or '') ~= 'none' then local pvalue = args['geo-data-type'] or 'P1082' local plabel = mw.wikibase.getLabel(pvalue) if not args['geo-number-size'] then args['geo-number-size']=12 end if not args['label-posD'] then args['label-posD'] = 'on' end qvalue=args['geo-data'..argval] -- total poulation is a Qvalue wikidata page entry, or add up all the geo-data values or use a random 10000 local region = args['geo-region'] -- or mw.wikibase.getEntityIdForCurrentPage() local thisPop=0 local areatxt='' local yearVal='' if mw.wikibase.getBestStatements(qvalue,pvalue)[1] then thisPop=tonumber(mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.amount) or 0 if pvalue == 'P2046' then -- area, so convert from ha to sq km if needed (/100) thisPop = convertToSqKm(thisPop, mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.unit) end if mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers and mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers.P585 then yearVal = mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers.P585[1].datavalue.value.time if yearVal then yearVal = yearVal:sub(2,5) end -- wikidata reports an entire date and time, so extract the year end else debugmsg(mw.addWarning(mw.wikibase.getLabel(qvalue)..' ('..qvalue..') has no recorded '..plabel)) end local scalefactor=args['geo-scalefactor'] or 1 if argval=='1' and args['mark-size1'] and not args['mark-sizeD'] then args['mark-sizeD'] = args['mark-size1'] end if argval=='1' and args['label-pos1'] and not args['label-posD'] then args['label-posD'] = args['label-pos1'] end if not args['mark-size'..argval] then if args['show-geo-dots'] == '0' then args['mark-size'..argval] = args['mark-sizeD'] or '1' -- just use a 'point' for the label to attach to else if thisPop>0 then args['mark-size'..argval] = round(math.sqrt(thisPop/geoTotalPop*100) * 8 * scalefactor,0) else args['mark-size'..argval] = args['mark-sizeD'] or '1' args['shape-outline'..argval]=args['shape-outline'..argval] or args['shape-outlineH'] end end end local posTemp=string.lower(multiCheck(args,'label-pos',argval,'on')) args['label-pos'..argval] = posTemp posTemp=splitItem(posTemp,2)[1] if pvalue == 'P2046' then thisPop = round(thisPop,2) areatxt=sqkm..areatxt -- round decimals if sq km area elseif pvalue == 'P10622' then if mw.wikibase.getBestStatements(qvalue,pvalue)[1] then local punit=(mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.unit or ''):match("Q.*") -- extract Q value if punit == 'Q4917' then areatxt=' US$' elseif punit == 'Q4916' then areatxt=' euro' elseif punit == 'Q25224' then areatxt=' £GBP' else areatxt=mw.wikibase.getLabel(punit)..areatxt end end end if posTemp=='off' then else if not args['label'..argval] then args['label'..argval] = mw.wikibase.getLabel(qvalue) end local tline=posTemp:find("[%+%^%-]") if tline then local tval=posTemp:sub(tline,tline) local eline posTemp=posTemp:sub(1,tline-1) if posTemp == 'left' or posTemp == 'right' or string.find(posTemp,'east') or string.find(posTemp,'west') then if tval == '^' then eline = '^' else eline = ' ' end -- only add line break if 'forced', used for left/right elseif tval == '+' or tval == '^' then eline = '^' -- add linebreak unless 'forced' not to else eline = ' ' -- must be a dash, so no line break end args['label'..argval] = args['label'..argval]..eline..'('..formatInt(thisPop)..')' args['label-pos'..argval] = posTemp..args['label-pos'..argval]:sub(tline+1,#args['label-pos'..argval]) end end if posTemp=='on' then if argval=='1' and (not args.ldyD) then args.ldyD = args['ldy'..argval] or '0' end if not args['ldy'..argval] then args['ldy'..argval] = args.ldyD end -- ensure ldy for that dot is added to the position args['label-pos'..argval]='center' local _,br = string.gsub(args['label'..argval],'%^','') local item=splitItem(args['label-size'..argval],2) args['ldy'..argval] = (args['ldy'..argval] or 0) + ((item[1] or 11) * ((br+1)*0.6)) + ((args['geo-number-size'] or 12) / 2) end local nt='' if args['mark-title'..argval] == 'nolist' then nt='nolist,' end if not args['mark-title'..argval] or args['mark-title'..argval] == '' or args['mark-title'..argval] == 'nolist' then if mw.wikibase.getSitelink(qvalue) then args['mark-title'..argval] = nt..'[['..mw.wikibase.getSitelink(qvalue)..']]' else args['mark-title'..argval] = nt..mw.wikibase.getLabel(qvalue) end end -- If mark-title is not set locally then attempt Sitelink value, or use label if thisPop == 0 then areatxt='' end captionTableText[argval]={} captionTableText[argval][1]= args['mark-title'..argval] captionTableText[argval][2]= formatInt(thisPop) if maplist.width >= 280 then captionTableText[argval][3]= yearVal else captionTableText[argval][3]='no-year' end if args['show-q-values'] == '1' then captionTableText[argval][4]='<small>[[wikidata:'..qvalue..'|'..qvalue..']]</small>' else local nbsp = '&nbsp;' if maplist.width < 230 then nbsp = ' ' end captionTableText[argval][4]='' if captionTableText[argval][3] == 'no-year' then captionTableText[argval][2] = captionTableText[argval][2]..'<small><sup>'..nbsp..'[[wikidata:'..qvalue..'|WD]]</sup></small>' else captionTableText[argval][3] = captionTableText[argval][3]..'<small><sup>'..nbsp..'[[wikidata:'..qvalue..'|WD]]</sup></small>' end end if args['mark-title'..argval] == 'none' then -- no title else args['mark-title'..argval] = args['mark-title'..argval]..' ('..plabel..' '..formatInt(thisPop)..areatxt..')' end if not args['mark-description'..argval] then args['mark-description'..argval] = (mw.wikibase.getDescription(qvalue) or '')..'<br>' end if (not args['mark-image'..argval]) and (mw.wikibase.getBestStatements(qvalue,'P18')[1]) then args['mark-image'..argval] = mw.wikibase.getBestStatements(qvalue,'P18')[1].mainsnak.datavalue.value end item = multiCheck(args,'shape',argval,'circle') --default to circle if geo-data if not string.find(item,'svgpath,') then item=string.lower(item) end -- ensure svgpath items retain original case values else item = multiCheck(args,'shape',argval,'image') if not string.find(item,'svgpath,') then item=string.lower(item) end -- ensure svgpath items retain original case values end if string.find(item,'n-',0,true)==1 or string.find(item,'l-',0,true)==1 then autoDotTag=string.sub(item,0,1) item=string.sub(item,3) end if item == 'image' then shapeResult[nval].shape = 'image:' shapeResult[nval].shapeFile =multiCheck(args,'mark',argval,'Red pog.svg') shapeWidth=-1 else shapeResult[nval].shape = item end item= multiCheck(args,'mark-size',argval,(shapeResult[nval].shapeSize or '14px')) -- add geo-data as fallback if already assigned local a,b,c= getsize(string.gsub(string.gsub(item,',','px')..'px','pxpx','px')) if b==a and args['mark-dim'..argval] then b= b / tonumber(string.match(args['mark-dim'..argval],"[%.%-?%d]+")) end shapeHeight=b/2 item=tostring(a)..'px'..tostring(b)..'px'..tostring(c)..'px' shapeResult[nval].shapeSize= item itemTab=splitItem(multiCheck(args,'shape-color',argval,'hard red'),2) shapeResult[nval].shapeColor=itemTab[1] or 'hardred' item=itemCheck(itemTab[2],'%') -- jump through the various opacity hoops and add to color if needed if not item then item=itemCheck(args['shape-opacity'..argval],'%') end if item and item~='0%' and item~='100%' then shapeResult[nval].shapeColor=shapeResult[nval].shapeColor..item end shapeResult[nval].shapeAngle=itemCheck(multiCheck(args,'shape-angle',argval,'0'),'deg') or '0deg' if args['shape-angle'..argval] then shapeResult[nval].shapeAngleArg=true else shapeResult[nval].shapeAngleArg=false end --sort out the outline entry itemTab=splitItem(multiCheck(args,'shape-outline',argval,'transparent,0,100,solid'),4) shapeResult[nval].outlineColor=itemTab[1] or 'dark grey' shapeResult[nval].outlineWidth=itemCheck(itemTab[2],'px') or '1px' if itemTab[3] and itemTab[3]~='100' and itemTab[3]~='0' then shapeResult[nval].outlineColor=shapeResult[nval].outlineColor..itemCheck(itemTab[3],'%') end shapeResult[nval].outlineStyle=itemTab[4] or 'solid' -- label size, background, outline shapeResult[nval].textAT=args['label-attribute'..argval] or '' itemTab=splitItem( multiCheck(args,'label-size',argval,'12'),3) shapeResult[nval].textSZ=itemCheck(itemTab[1],'px') or '12px' if itemTab[2]=='outline' then shapeResult[nval].textBG=itemTab[3] or 'transparent' shapeResult[nval].textOL='1px' elseif itemTab[3]=='outline' then shapeResult[nval].textBG=itemTab[2] or 'transparent' shapeResult[nval].textOL='1px' else shapeResult[nval].textOL='0px' shapeResult[nval].textBG=itemTab[2] or 'transparent' end if getColor(shapeResult[nval].textBG)==CTB['hardgrey'] and shapeResult[nval].textBG~='hardgrey' then shapeResult[nval].textBG= 'transparent' end --label color etc itemTab=splitItem(multiCheck(args,'label-color',argval, 'darkgrey','label-colour'),2) shapeResult[nval].textCL=itemTab[1] or 'darkgrey' if itemTab[2] and itemTab[2]~='0%' and itemTab[2]~='100%' then shapeResult[nval].textCL=shapeResult[nval].textCL..itemTab[2] end shapeResult[nval].textSP=itemCheck( multiCheck(args,'label-spacing',argval,'0'),'px') -- sets letter-spacing in px shapeResult[nval].textLH=itemCheck( multiCheck(args,'label-height',argval,'120'),'%') -- sets line height, 120% default shapeResult[nval].textNG=itemCheck(multiCheck(args,'label-angle',argval,'0'),'deg') if args['label-angle'..argval] then shapeResult[nval].textNGarg=true else shapeResult[nval].textNGarg=false end --sgd=dotTag attributes shapeResult[nval].tagSize=(args['geo-number-size'] or tostring(shapeHeight*1.5))..'px' local c1,c2=checkColors(shapeResult[nval].shapeColor) shapeResult[nval].tagColor=c2 shapeResult[nval].tagSpacer='0px' shapeResult[nval].tagAngle='0deg' -- sge extension line attributes local shapePos=splitItem(multiCheck(args,'label-pos',argval,'right'),6) if shapePos[2]=='with-line' or shapePos[2]=='n-line' then shapeResult[nval].textEW=(shapePos[3] or '1')..'px' -- width shapeResult[nval].textEC=(shapePos[4] or shapeResult[nval].shapeColor or 'darkgrey') elseif shapePos[2]=='photo-panel' then shapeResult[nval].textEW='2px' -- width shapeResult[nval].textEC=shapeResult[nval].textCL else shapeResult[nval].textEW='0px' -- width shapeResult[nval].textEC='grey'-- colour end shapeResult[nval].textES='solid' if argval=='H' then return dotResult end --Assign dot values local dotItem={} dotItem.group=nval dotItem.code=nval dotItem.posType=shapePos[2] or 'nil' if (shapePos[2] or '') =='photo-panel' then dotItem.ppwidth= tonumber(string.match((shapePos[4] or '110'),"%d+")) dotItem.ppheight= tonumber(string.match((shapePos[5] or '48'),"%d+") ) dotItem.photowidth=round(tonumber(string.match((shapePos[3] or '1.3'),"[%.%-?%d]+")) * dotItem.ppheight+1,0) dotItem.photoImage=args['mark-image'..argval] --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end if (shapePos[2] or '') =='mark-line' then local x=tonumber(argval or '1') dotItem.markDest=shapePos[6] or tostring(x-1) dotItem.mlWidth= tonumber(string.match((shapePos[3] or '1'),"%d+")) dotItem.mlStyle= shapePos[4] or 'solid' dotItem.mlGap=tonumber(string.match((shapePos[5] or '0'),"[%d]+")) shapeResult[nval].textEC=shapeResult[nval].outlineColor or 'darkgrey' end if args['mark-coord'..argval] then itemTab=splitItem(convertCoordsTrad (args['mark-coord'..argval]),2) if itemTab[1] == nil or itemTab[2] == nil then debugmsg(mw.addWarning('Unable to read coordinates for mark-coord'..argval)) else dotItem.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) or 0 end else dotItem.lat=tonumber(string.match(args['mark-lat'..argval],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(args['mark-lon'..argval],"[%.%-?%d]+")) or 0 end if args['dateline'..argval] and (args['dateline'..argval]=='1' or args['dateline'..argval]=='-1') then dotItem.lon=dotItem.lon+(tonumber(args['dateline'..argval] ) *360) end maplist.lon=dotItem.lon maplist.lat=dotItem.lat dotItem.gridx, dotItem.gridy = maptogrid(maplist,1) local item=args['mark-title'..argval] or '' -- sort out the caption, wikilink and plaintext tooltip items from dotLink if item=='none' then dotItem.param1='nomap nolist' item='' end if string.find(item,'nolist') then dotItem.param1='nolist,' item = string.sub(args['mark-title'..argval],8) or args['label'..argval] end dotItem.dotLink=item if item ~= '' then item=stripdivs(item) dotItem.title=delink({item}) local linkstart= string.find(item,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then dotItem.dlink=delink({string.sub(item,linkstart,string.find(item,']]',1,true)+1),wikilinks='target'}) -- debugmsg('dlink for '..item..' is '..dotItem.dlink) else dotItem.dlink='' end else dotItem.dlink='' dotItem.title='' end if autoDotTag=='n' then item=nval elseif autoDotTag=='l' then item=string.char(64+tonumber(nval)) else item='' end dotItem.dotTag = args['numbered'..argval] or item if shapePos[2]=='n-line' and (args['label'..argval] or args['label'..argval]=='') then if dotItem.dlink =='' then item=dotItem.dotTag..' '..args['label'..argval] else item='[['..dotItem.dlink..'|'..dotItem.dotTag..']] '..args['label'..argval] end else item=(args['label'..argval] or '') end if args['labela'..argval] then item = item..'^'..args['labela'..argval] end if args['labelb'..argval] then item = item..'^'..args['labelb'..argval] end local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end dotItem.labelText = a -- convert hats to line breaks if argval=='' then item = (args['label-offset-x']) or (args.ldx) or '0' else item=args['label-offset-x'..argval] or args['ldx'..argval] or args['label-offset-xD'] or args.ldxD or args['label-offset-x1'] or args.ldx1 or '0' end dotItem.dx=tonumber(string.match(item,"[%.%-?%d]+")) if argval=='' then item = (args['label-offset-y']) or (args.ldy) or '0' else item=args['label-offset-y'..argval] or args['ldy'..argval] or args['label-offset-yD'] or args.ldyD or args['label-offset-y1'] or args.ldy1 or '0' end dotItem.dy=tonumber(string.match(item,"[%.%-?%d]+")) dotItem.labelPos=shapePos[1] if args['mark-image'..argval] then dotItem.imageName= args['mark-image'..argval] end dotItem.info=args['mark-description'..argval] or '' table.insert(dotResult,1,dotItem) -- add to start of list, so they are in reverese order for displaying -- debugmsg(dotItem.code..' = '..dotItem.lat..','..dotItem.lon..': '..dotItem.labelText..' - '..dotItem.title..', '..shapeResult[nval].shapeSize) return dotResult end local function tradstyleParseShapes(args,dotTable,dotmax) local sgNumbers,sgSortable={},{} for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['geo-data'..x] and (not args['mark-coord'..x]) and (not args['mark-lat'..x]) then -- get lat and lon values from wikidata and put them into the args.lat and args.lon for use wherever needed if (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1]) then args['mark-lat'..x] = (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1].mainsnak.datavalue.value.latitude) or '0' args['mark-lon'..x] = (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1].mainsnak.datavalue.value.longitude) or '0' local p=args['geo-data-type'] or 'P1082' if mw.wikibase.getBestStatements(args['geo-data'..x],p)[1] then local n = tonumber(mw.wikibase.getBestStatements(args['geo-data'..x],p)[1].mainsnak.datavalue.value.amount) or 0 if p == 'P2046' then -- area, so convert to sq km if needed (/100) n = convertToSqKm( n, mw.wikibase.getBestStatements(args['geo-data'..x],p)[1].mainsnak.datavalue.value.unit) end geoTotalPop = geoTotalPop + n end else debugmsg(mw.addWarning('could not find coords for '..mw.wikibase.getLabel(args['geo-data'..x])..', '..args['geo-data'..x])) end end if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) then sgNumbers[x]=x -- add it to the list end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form (needs a second table, seemingly) if args['mark-coord'] or (args['mark-lat'] and args['mark-lon']) then table.insert(sgSortable,'0') end -- add it to the end of the list local default={} default.D={} local dotResult={} for k,sgnum in pairs(sgSortable) do -- work through the sorted list, parsing each set of shapes in turn, from 1 upwards shapeList[sgnum]={} dotTable=assignTradstyleShape(shapeList,default,dotTable,args,sgnum) end dotTable=assignTradstyleShape(shapeList,default,dotTable,args,'H') -- construct an extra highlight shapeitem local unitConvert=1 local areaUnits='' if args['geo-region'] and (args['find-geo-dots'] or '1') ~= '0' then local region = args['geo-region'] -- or mw.wikibase.getEntityIdForCurrentPage() local pvalue = args['geo-data-type'] or 'P1082' local plabel = mw.wikibase.getLabel(pvalue) local subhead='' -- Assemble the subhead content, a line of text about the region as a whole if region and mw.wikibase.getBestStatements(region,pvalue)[1] then local punit=mw.wikibase.getBestStatements(region,pvalue)[1].mainsnak.datavalue.value.unit:match("Q.*") -- extract Q value if pvalue == 'P10622' then -- add other currency-based pvalues here areaUnits=mw.wikibase.getLabel(punit) end if punit == 'Q35852' then unitConvert=100 areaUnits = ' '..sqkm -- if its hectares, convert to sq km elseif punit == 'Q232291' then unitConvert = (1/2.59) areaUnits = ' '..sqkm -- or this if it's square miles elseif punit == 'Q81292' then unitConvert = 247.1 areaUnits = ' '..sqkm -- or this if it's acres elseif punit == 'Q712226' then unitConvert = 1 areaUnits = ' '..sqkm -- or this if it's already sq km elseif punit == 'Q4917' then areaUnits=' US$' -- replace long version with some shortform currencies () elseif punit == 'Q4916' then areaUnits=' euro' elseif punit == 'Q25224' then areaUnits=' £GBP' end -- if pvalue == 'P2046' then -- may need to add other instances of sq km so add in other P-items here if they need units showing -- end geoTotalPop=(mw.wikibase.getBestStatements(region,pvalue)[1].mainsnak.datavalue.value.amount) / unitConvert local yearVal if mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers then yearVal = mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P585[1].datavalue.value.time if yearVal and yearVal:sub(1,1) == '+' then yearVal = yearVal:sub(2,5) if mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459 and mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459[1] then local methodVal = mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459[1].datavalue.value.id if methodVal then yearVal=yearVal..' '..mw.wikibase.getLabel(methodVal) end end yearVal = ' ('..yearVal..')' else yearVal ='' end end subhead=totalItems..' '..mw.wikibase.getLabel(region)..' '..plabel..' ([[wikidata:'..region..']]) = '..formatInt(round(geoTotalPop),1)..areaUnits..yearVal else if pvalue == 'P2046' then areaUnits = sqkm end subhead=totalItems..' '..mw.wikibase.getLabel(region)..' '..plabel..' ('..sumOfDatapoints..') = '..formatInt(round(geoTotalPop),1)..areaUnits end if maplist.width < 350 and #plabel > 4 then plabel=string.sub((plabel or ' '),1,3)..'.' end if maplist.width < 330 then numberCol = ' ' end if maplist.width < 280 then year = 'no-year' end captionTable = subhead if (args['show-q-values'] or '') ~= '1' then captionTable = captionTable..'<div style="float:right;"><small> (WD&nbsp;=&nbsp;'..wikidataLink..')</small></div><br>' wikidataLink='' end captionTable = captionTable..'<table class="wikitable plainrowheaders sortable mw-collapsible mw-collapsed" style="width: 100%;"><caption>'..(args.toggletext or toggletext)..' </caption>' captionTable = htmlTableLine(captionTable,numberCol,mw.wikibase.getLabel(region),plabel..' '..areaUnits, year, wikidataLink,'header') end return dotTable end local function checkfortooltip (title,dx,dy,dotlabel,dlink,nolabel) -- returns tlink if available, and dlink, if needed and tshape=true if shape needed local tshape,tlink = false,"" if dlink~='' and not nolabel then tlink=dlink end if (tlink=="" or nolabel) and title~="" then tshape=true end -- tshape flags True if title is wanted for shape if (not (dx==0 or dy==0) or dotlabel=='') and title~='' then tshape=true end -- add tooltip to shape if its number has moved return tshape,tlink end local function tshift(angle) -- adjustment to place text near the centre of a triangle, shifted to allow rotation of triangle shape local x=tonumber(string.match(angle,"%-?%d+")) if x<0 then x=360+x end -- set to a single degree direction, 0 to 360 if x>359 then return 0,0 end -- shift the centre of the triangle based on rotation value if x <=40 or x>=320 then return -0.17,0 -- triangle up= -shiftv elseif x>=140 and x<=220 then return 0.17,0 --triangle down= +shiftv elseif x >220 then return 0,-0.17 --triangle left= -shifth elseif x >40 then return 0,0.22 --triangle right= +shifth end return 0,0 end local function makeTriangle(result,row,shape,outline,tlink) local w,h,r=getsize(shape.shapeSize) if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) w=w+p*2 h=h+p*2 end table.insert(result,'<div ') if tlink then table.insert(result,' title="'..row.title..'" ') end table.insert(result,'style="display:inline-block; position: absolute') if shape.shapeAngle ~= '0deg' then table.insert(result,'; transform: rotate('..shape.shapeAngle..')') end local shiftv,shifth=0,0 shiftv,shifth=tshift(shape.shapeAngle) table.insert(result,'; top: '..tostring(row.gridy-h/2+h*shiftv)..'px') table.insert(result,'; left: '..tostring(row.gridx-w/2+w*shifth)..'px; width: 0; height: 0; outline-width: 0px') table.insert(result,'; border-left: '..tostring(w/2)..'px solid transparent') table.insert(result,'; border-right: '..tostring(w/2)..'px solid transparent') if outline then -- fill with outline colour, to make a 'base layer' or shape colour table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.outlineColor).. '">') else table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.shapeColor).. '">') end table.insert(result,'</div>') end local function makeSquare(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) if shape.shape=='square' then h=w end -- squares are always square!. box can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end if r~=0 then div:css('border-radius',tostring(r).."px") end if shape.shape=='panel' then div:css('top', tostring(row.gridy).."px") div:css('left', tostring(row.gridx).."px") else div:css('top', tostring(row.gridy-h/2).."px") div:css('left', tostring(row.gridx-w/2).."px") end div:css('width', tostring(w).."px") div:css('height', tostring(h).."px") div:css('background-color', getColor(shape.shapeColor) ) div:css('color', 'inherit') table.insert(result,tostring(div)) end local function makeCircle(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding if shape.shape=='circle' then h=w end -- circles are always round. ellipse can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end div :css('top', tostring(row.gridy-h/2).."px") :css('left', tostring(row.gridx-w/2).."px") :css('width', tostring(w).."px") :css('height', tostring(h).."px") :css('border-radius', "50%") :css('background-color', getColor(shape.shapeColor) ) :css('color', 'inherit') table.insert(result,tostring(div)) end local function makeRuleA(result,row,shape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local lineV=0 if shape.shape=='rulea' then lineV=oWid*3+16 end table.insert(result,"<div style=\"display:inline-block; position: absolute") -- create a square transparent container, which will rotate line and arrow together table.insert(result,"; top:"..tostring(row.gridy - w/2).."px") table.insert(result,"; left:"..tostring(row.gridx - w/2).."px") table.insert(result,"; width:"..tostring(w).."px") table.insert(result,"; height:"..tostring(w).."px; background:transparent; color:inherit;") table.insert(result,"; transform: rotate( "..tostring(tonumber(string.match(shape.shapeAngle,"[%.%-?%d]+")) - 90).."deg);\">" ) table.insert(result,"<div style=\"display:inline-block; position: absolute") -- put the line (as a border-right) across the container table.insert(result,"; top:"..tostring(lineV).."px") table.insert(result,"; left:"..tostring((w - oWid )/2).."px; width: 0px") table.insert(result,"; height: "..tostring(w -lineV).."px") table.insert(result,"; border-right: "..shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) table.insert(result,"; background:transparent; color:inherit;\"></div>") if shape.shape=='rulea' then table.insert(result,"<div style=\"display:inline-block; position: absolute; top: 0px") --and add arrow head table.insert(result,"; left:"..tostring(w/2-( oWid/2)-oWid*0.55-2).."px; width: 0; height: 0; outline-width: 0px") table.insert(result,"; border-left: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-right: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-bottom: "..tostring(oWid*3+16).."px solid "..getColor(shape.outlineColor).."\"></div>") end table.insert(result,"</div>") end local function makeCurveA(result,row,shape) -- draw a curve with Arrow ----- local w,h=getsize(shape.shapeSize) -- = width,height local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local Angle=tonumber(string.match(shape.shapeAngle,"[%.%d]+")) table.insert(result,'<div style="position: absolute;') --set up out div, which will allow the whole to rotate table.insert(result,'top:'..tostring(row.gridy - (w + oWid*3+16)/2)..'px;') table.insert(result,'left:'..tostring(row.gridx - ( w + oWid*3+16)/2)..'px; ') table.insert(result,'width: '..tostring(w+oWid*3+16)..'px; ') table.insert(result,'height: '..tostring(w+oWid*3+16)..'px; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate( '..tostring(Angle-120)..'deg);">') else table.insert(result,'transform: rotate( '..tostring(Angle -62)..'deg);">') end table.insert(result,'<div style="position: absolute;') --set up div for the rounded corner of a rectangle table.insert(result,'border-left: '..shape.outlineWidth..' '..shape.outlineStyle..' '..getColor(shape.outlineColor)..';') if shape.shape=='curvea' then table.insert(result,'border-radius: 10000px 0 0 '..tostring(w)..'px; top:0px; left:'..tostring(w*0.25)..'px;') else table.insert(result,'border-radius: '..tostring(w)..'px 0 0 10000px;') table.insert(result,'top:'..tostring((oWid*3+16)/2+w*0.15)..'px; left:'..tostring(w*0.25)..'px;') end -- and add a triangular arrow head table.insert(result,'width: '..tostring(w)..'px; height: '..tostring(w)..'px;"></div><div style="position: absolute; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate(180deg); top:'..tostring(w-1)..'px; ') else table.insert(result,'transform: rotate(0deg); top: '..tostring(0-( ( oWid*3+16)/2)+1+( w*0.15) )..'px;') end -- reverse table.insert(result,'left:'..tostring(0-( oWid*0.6)-2+(w*0.25))..'px;') table.insert(result,'width: 0; height: 0; outline-width: 0px; border-left: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-right: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-bottom: '..tostring(oWid*3+16)..'px solid '..getColor(shape.outlineColor)..';"></div></div>') end local function makeLineTo (result,x1,y1,x2,y2,oWid, oStyle, oCol,double,shape) table.insert(result,"<div style=\"display:inline-block; position: absolute;") -- draw a line between x1,y1 and x2,y2, px-coords where 0,0 is centre of frame -- Maths calculations thanks to ES local tw if double <= 1 then tw = tonumber(string.match(oWid,"[%.%d]+"))/2 -- shift centre by half the line width else tw = tonumber(string.match(oWid,"[%.%d]+")) + (double/2) - 0.5 -- shift centre by a line width + half the gap end -- debugmsg(oWid..'+'..double..'/2='..tw) table.insert(result,"left: "..tostring(x1+( (x2-x1)/2) - (math.sqrt( ( x2-x1)^2 + (y2-y1)^2 )/2) ).."px;") table.insert(result,"top: "..tostring(y1+( ( y2-y1 )/2) - tw).."px;") table.insert(result,"width: "..tostring(math.sqrt( (x2-x1 )^2 + ( y2-y1 )^2) ).."px;") table.insert(result,"height: "..tostring(double).."px; background-color:transparent; color:inherit; ") table.insert(result,"outline-width: 0; border-bottom: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) if double>1 then table.insert(result,"border-top: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) end local lineAngle=math.atan(( y2-y1)/( x2-x1 ) )*180/math.pi -- if x1 == x2 then lineAngle=90 end table.insert(result,"transform: rotate("..lineAngle.."deg);\"></div>") -- debugmsg('text angle started as '..shape.textNG..' and lineAngle = '..lineAngle) if (lineAngle < -90) or (lineAngle >90) then lineAngle=lineAngle-180 end if shape and not shape.textNGarg then shape.textNG=tostring(round(lineAngle,0))..'deg' end -- Put in line angle for a default text angle as well -- if (y2 >= y1) and (x2 <= x1) then lineAngle=lineAngle-180 debugmsg('arrow angle corrected') end if x2 < x1 then lineAngle=lineAngle-180 end if shape and not shape.shapeAngleArg and shape.shape == 'arrowhead' then shape.shapeAngle=tostring(round(lineAngle,0))..'deg' end -- line angle for arrowhead -- debugmsg('text angle is now '..shape.textNG..'with arrow at '..lineAngle) end local function makeClipPath(result,row,shape,outline,tshape) --tshape is a flag to show if the tooltip (title=) is wanted -- return the text css div code to position and draw a shape occupying a specified clippath if string.find(shape.shape, 'svgpath,') and #shape.shape > 9 then pathshape.svgpath=string.sub(shape.shape,9) shape.shape='svgpath' end if not pathshape[shape.shape] then debugmsg(mw.addWarning('shape'..row.code..' = '..shape.shape..'. The shape name is not defined')) return end local w,h,r=getsize(shape.shapeSize) if shape.shape == 'arrowhead' then -- enlarge arrowheads, as they only go to the middle of the notional shape-size w=w*2.5 h=h*2.5 end if string.match(shape.shape,"circle") or string.match(shape.shape,"square") then h=w end -- use ellipse and box for stretched shapes local shifth,shiftv = 0,0 if string.match(shape.shape,"triangle") then shiftv,shifth =tshift(shape.shapeAngle) end if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) or 0 w=w+p*2 h=h+p*2 end table.insert(result,"<div ") if tshape then -- Add tooltip if needed table.insert(result," title=\""..row.title.."\" ") end table.insert(result,"style=\"display:inline-block; position: absolute; background-color:") if outline then table.insert(result,getColor(shape.outlineColor)) -- fill with outline colour, to make a 'base layer' else table.insert(result,getColor(shape.shapeColor)) end table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;"..pathshape[shape.shape].."&#39;) ") -- adds the required clippath data from the table of pathshape string literals table.insert(result,"; top:"..tostring(row.gridy - 10 + h*shiftv).."px") table.insert(result,"; left:"..tostring(row.gridx - 10 + w*shifth).."px") table.insert(result,"; width:20px") -- needs to be a path within a 20px20px box, and then rescales using size values to match other shape sizes table.insert(result,"; height:20px; transform:") if shape.shapeAngle ~= "0deg" then table.insert(result," rotate("..shape.shapeAngle..")") end table.insert(result," scale("..tostring(w/16)..", "..tostring(h/16)..")") table.insert(result,"\"></div>") end local function makeImage(result,row,shape) local w,h,r=getsize(shape.shapeSize) local image=shape.shapeFile if not image or image=='' then image='Red pog.svg' end local imagediv=mw.html.create ('div') imagediv:css('position', "absolute") if shape.shapeAngle ~= "0deg" then imagediv:css('transform',"rotate("..shape.shapeAngle..")") end imagediv :css('top', (row.gridy+2 + math.min(h/2-12,0) - h/2).."px") --File seems to adjust pos for small images :css('left', (row.gridx-1-w/2).."px") :css('background-color', "transparent" ) :css('color','inherit') :wikitext('[[file:'..image..'|'..tostring(w+2)..'px|alt='..(row.title or '')..'|link=]]') table.insert(result,tostring(imagediv)) end local function makePhotoPanel(result,row,shape) local h=row.ppheight table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;' ) table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') table.insert(result, 'width: '..tostring(row.ppwidth)..'px; height: '..tostring(h)..'px; border-radius: 2px; color:inherit;') table.insert(result, 'background-color: #E8E8D6; outline: 2px solid '..getColor(shape.textCL)..'; box-shadow: 2px 2px 4px #33203335;"></div>') if row.photoImage and row.photowidth >0 then table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;') if row.labelPos=='left' or string.find(row.labelPos,'west') then table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') row.dx=row.dx+row.photowidth/2 else table.insert(result, 'left: '..tostring(row.gridx+row.dx + (row.ppwidth-row.photowidth) - row.ppwidth/2)..'px;') row.dx=row.dx-row.photowidth/2 end table.insert(result, 'background-color:transparent; color:inherit; border-radius: 2px;">') table.insert(result,'[[File:'..row.photoImage..'|x'..tostring(h)..'px|File:'..row.photoImage..']]</div>') row.labelPos='center' end end local function makePanelText(result, row, shape) local w,h,r=getsize(shape.shapeSize) local ty=tonumber(string.match(shape.textSZ or '11',"%d+") ) table.insert(result,'<div style="position:absolute; line-height: 120%; font-size: '..shape.textSZ..'; color:'..getColor(shape.textCL)..';') if row.labelPos == 'left' or string.find(row.labelPos,'west') then table.insert(result,'top: '..tostring(row.dy+row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+3)..'px; text-align: left; width:'..tostring(w)..'px;') elseif row.labelPos == 'right' or string.find(row.labelPos,'east') then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+w-3)..'px; text-align: right; width: max-content; transform: translateX(-100%);') elseif row.labelPos == 'top' or row.labelPos== 'north' then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') elseif row.labelPos == 'bottom' or row.labelPos=='south' then local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*1.1 table.insert(result,'top: '..tostring(row.gridy+w-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') else -- center or centre local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*0.6 table.insert(result,'top: '..tostring(row.gridy+(h/2)-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') end if shape.textSP and shape.textSP ~='0px' then table.insert(result,"letter-spacing: "..shape.textSP..';') end if shape.textLH and shape.textLH ~='120%' then table.insert(result,"line-height: "..shape.textLH..';') end table.insert(result,"vertical-align: middle;\">"..row.labelText.."</div>") end local function makeTextItem(result, row, shape, align, tlink, textItem, dotItem) local w,h,r=getsize(shape.shapeSize) table.insert(result,"<div ") if row.title ~= "" then table.insert(result," title=\""..row.title.."\" ") end local ty,bry,linkoffset = 0,0,0 local compy=0 local lh=tonumber(string.match(shape.textLH or '120',"%d+"))/100 if dotItem==1 or (dotItem==2 and row.posType~='n-line') then -- if there is a dotTag in the middle of the shape then use the tag settings ty=tonumber(string.match(shape.tagSize,"%d+")) or 0 table.insert(result,"style=\"position:absolute; line-height: 120%; top: "..tostring(row.gridy-ty*0.62)) table.insert(result,"px; left: "..tostring(row.gridx).."px; width: fit-content; ") table.insert(result,"text-align: center; color: "..getColor(shape.tagColor).."; background-color: transparent;") local trf="" if shape.tagAngle ~="0deg" then trf=" rotate("..shape.tagAngle..")" end table.insert(result, "transform: translateX(-50%)"..trf.."; font-size: "..shape.tagSize..";") if shape.tagSpacer~='0px' then table.insert(result, "letter-spacing:"..shape.tagSpacer..";") end else -- or add tfx settings for left, right or center align, colors, backgrounds, border-outline table.insert(result,'style="position:absolute; ') if dotItem==2 then -- dotTag is out at x,y so 85% ty=tonumber(string.match(shape.tagSize or '0',"%d+")) table.insert(result,'font-size: '..shape.tagSize..'; padding:0px 2px;line-height: 85%; top: '..tostring(row.dy+row.gridy-ty*0.52)) else -- it is labelText, so use textLH or 120% ty=tonumber(string.match(shape.textSZ or '11',"%d+")) if row.labelPos=='northwest' or row.labelPos=='northeast' then compy=-ty elseif row.labelPos=='southeast' or row.labelPos=='southwest' then compy=ty/2 end if row.labelPos and not (row.labelPos== 'auto' or row.labelPos=='') then bry=(select(2, string.gsub(row.labelText,"<br>", ""))*lh) -- is it a multiline text? expand by line-height /120%? if row.posType == 'with-line' then bry=0 end if row.labelPos=='bottom' or row.labelPos == 'south' then bry = 0 -- and shift by none, all or half elseif row.labelPos=='top' or row.labelPos == 'north' then if shape.shape=='image:' then bry= 1 + math.min(w/2-10,0)-bry*ty else bry= -bry*ty+2 end else bry=-bry*(ty/2 * lh) end end if row.posType == 'photo-panel' then bry=bry+3 end table.insert(result,'font-size: '..shape.textSZ..'; padding:0px 3px;line-height: '..shape.textLH..'; ') table.insert(result,'top: '..tostring(row.dy+row.gridy+compy+bry-ty*lh/2)) end table.insert(result,"px; left: "..tostring(row.dx+row.gridx).."px; color: "..getColor(shape.textCL).."; ") table.insert(result,"width: max-content; ") local trf="" if shape.textNG ~="0deg" then trf="rotate("..shape.textNG..")" end if shape.textOL~="0px" then table.insert(result,"background-color: "..getColor(shape.textBG).."; ") table.insert(result,"border: "..shape.textOL.." solid "..getColor(shape.textCL).."; border-radius:6px;") else table.insert(result,"background-color: transparent;") end if row.labelPos=="right" or string.find(row.labelPos,'east') then table.insert(result,"text-align: left; ") linkoffset=w if shape.textNG ~="0px" then table.insert(result,"transform-origin: left; transform: rotate("..shape.textNG.."); ") end elseif row.labelPos=="left" or string.find(row.labelPos,'west') then if shape.textNG ~="0px" then table.insert(result,"transform-origin: right;") end linkoffset=-w table.insert(result,"text-align: right; transform: translateX(-100%) "..trf.."; ") else table.insert(result,"text-align: center; transform: translateX(-50%) "..trf.."; ") end table.insert(result,'font-weight: normal; line-height: '..shape.textLH..'; letter-spacing:'..shape.textSP..'; vertical-align: bottom;') end if string.find(shape.textAT or '','bold') then textItem='<b>'..textItem..'</b>' end if string.find(shape.textAT or '','italic') then textItem='<i>'..textItem..'</i>' end if shape.textOL=='0px' and shape.textBG~='transparent' and dotItem==0 then table.insert(result,'\"><span style=\"background-color: '..getColor(shape.textBG)..'; color:inherit;\">'..textItem..'</span></div>') else table.insert(result,"\">"..textItem.."</div>") end if tlink~='' and not string.match(row.param1 or "","nolink") then table.insert(result,makeLinkBox(row.gridx+row.dx+linkoffset, row.gridy+row.dy+bry, 16, row.dotTag..' '..row.title,tlink)) end end local function getshapetable(row,shape) -- Construct CSS divs for a dot from shape and map data local result={} local w,h,rndg=getsize(shape.shapeSize) local tshape,tlink=checkfortooltip(row.title,row.dx,row.dy,row.dotTag,row.dlink,string.match(row.param1 or "","nolink") ) local align=row.labelPos or '' local offsetx,offsety=0,0 local ty=tonumber(string.match((shape.tagSize or 9),"%d+" )) if row.labelText and row.labelText~='' then ty=tonumber(string.match((shape.textSZ or 11),"%d+" )) else align='center' end -- it is just for dotTag, so justify center -- identify align value and extend offsets local widthzone,heightzone = (w/2)+1,(h/2)+1 local theta,r = math.deg( math.atan2(row.dy, row.dx)) , math.sqrt(row.dx^2 + row.dy^2) if align=='auto' or align=='' then if (theta < -112 or theta > 112) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "left" offsetx=1 elseif (theta > -68 and theta < 68) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "right" offsetx=-1 elseif theta <0 then offsety=ty/2-1 -- bottom else offsety=0-ty/2+1 -- top end elseif align=='left' or string.find(align,'west') then row.dx=row.dx - w/2 widthzone=4 offsetx=-1 elseif align=='right' or string.find(align,'east') then row.dx=row.dx + w/2 widthzone=4 offsetx=1 elseif align=='top' or align=='north' then if string.find(shape.shape,'curve') then row.dy=row.dy-(h/2)-12 heightzone=4 offsety=ty/2-1 else row.dy=row.dy - h-2 heightzone=4 offsety=ty/2-1 end elseif align=='bottom' or align=='south' then if string.find(shape.shape,'curve') then row.dy=row.dy+(h/2) heightzone=4 offsety=ty/2-1 else row.dy=row.dy + h+2 heightzone=4 offsety=0-ty/2-1 end end if shape.textEW ~= "0px" and not string.match(row.param1 or "","noline") then -- debugmsg('r-(w/2) > widthzone r-w/2='..tostring(r-(w/2))..', widthzone='..widthzone..', label='..(row.labelText or '')..', postype='..(row.posType or '')) makeLineTo(result, row.gridx+1, row.gridy-1, row.gridx+row.dx+offsetx, row.gridy+row.dy+offsety, shape.textEW, shape.textES,shape.textEC,1) -- debugmsg('n-line for '..row.code..':'..tostring(row.gridx+1)..', '..tostring(row.gridy-1)..', '..tostring(row.gridx+row.dx+offsetx)..', '..tostring(row.gridy+row.dy+offsety)..', with width '..tostring(shape.textEW)..'px '..(shape.textES or 'no style')..' and color '..shape.textEC) end if row.posType and row.posType=='mark-line' then if row.gridx2 and row.gridy2 then makeLineTo(result, row.gridx, row.gridy, row.gridx2, row.gridy2, tostring(row.mlWidth)..'px', row.mlStyle, getColor(shape.textEC),row.mlGap,shape) -- debugmsg('mark-line line drawn from '..row.code..' with width '..tostring(row.mlWidth)..'px '..(row.mlStyle or 'no style')..' and color '..getColor(shape.textEC)) end end if w ~= 0 then if shape.shape=='itriangle' then shape.shape='triangle' shape.shapeSize=tostring(w)..'px'..tostring(w/2)..'px' end if shape.shape=="triangle" then if shape.outlineWidth ~= "0px" then makeTriangle(result,row,shape,true,false) -- larger triangle to give the outline, if required end makeTriangle(result,row,shape,false,tshape) -- smaller triangle to fit over the top elseif shape.shape=="square" or shape.shape=="box" or shape.shape=='panel' then makeSquare(result,row,shape,tshape) elseif shape.shape=="circle" or shape.shape=="ellipse" then makeCircle(result,row,shape,tshape) elseif string.find(shape.shape,'image:')==1 then makeImage(result,row,shape) elseif shape.shape=="rulea" or shape.shape=='rule' then makeRuleA(result,row,shape) elseif shape.shape=="curvea" or shape.shape=="curvec" then makeCurveA(result,row,shape) else -- use a pathshape clipPath if shape.outlineWidth ~= "0px" then makeClipPath(result,row,shape,true,false) -- larger path-shape to give the outline, if required end makeClipPath(result,row,shape,false,tshape) end end if row.ppwidth and row.ppwidth>0 then makePhotoPanel(result,row,shape) end if shape.shape=='panel' and row.labelText then makePanelText(result, row, shape) else if row.dotTag and row.dotTag ~= "" then -- there is a dotTag if (row.dx==0 and row.dy==0) and w>0 then -- it is on the dot so if dotsize is not 0 any label is ignored makeTextItem(result, row, shape, align, tlink, '<b>'..row.dotTag..'</b>', 1) else if row.labelText and row.labelText~='' then -- tag and label both used makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.labelText, 0) else makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.dotTag, 2) -- tag is ouside the dot end end else if (row.labelText and row.labelText~='') then -- just the label. No tag makeTextItem(result, row, shape, align, tlink, row.labelText, 0) end end end if tlink and tlink~='' then table.insert(result,makeLinkBox(row.gridx, row.gridy,w+3,row.dotTag..' '..row.title,tlink)) end return table.concat(result) end local function getmapframecontent(args,use) local result, comma= {}, '' local propertyTable= {} local color=splitItem(string.lower( args['map-data-color'] or 'dark orange'), 2) if use == 'basemap' then table.insert(result, '[') propertyTable.stroke = "#000000" propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') if color[2] then propertyTable['stroke-opacity'] = tonumber(color[2] or '20')/100 end else propertyTable.title = args['map-data-text'] or args['map-data'] or '' propertyTable.stroke = getColor(color[1]) propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') propertyTable['stroke-opacity'] = tonumber(color[2] or '100')/100 end if args['map-data-inverse'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geomask', ids = args['map-data-inverse'], properties = {title = (args['map-data-text'] or ''), stroke = '#555555', fill = '#555555', ['fill-opacity'] = 0.1, ['stroke-width'] = 1, ['stroke-opacity'] = 0.5 } } table.insert(result, mapJson) comma=', ' end if args['map-data-heavy'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-heavy'], properties = { title = args['map-data-heavy'], stroke = propertyTable.stroke, ['stroke-width'] = 9, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.2 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data-light'] and args['map-data-light'] ~= '' then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-light'], properties = { title = args['map-data-light'], stroke = propertyTable.stroke, ['stroke-width'] = 3, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.4 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data'] then propertyTable['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.2 local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data'], properties = propertyTable } table.insert(result, comma .. mapJson) comma=', ' end if args['map-wdqs'] then -- inserts a SPARQL wdqs query to find geopoints, geoshapes etc from wikidata local mapSparql = mw.text.jsonEncode{ type = 'ExternalData', service = args['map-wdqs-type'] or 'geopoint', query = args['map-wdqs'], } table.insert(result, comma .. mapSparql) -- debugmsg('mapSparql = ' .. mapSparql) comma=', ' end if args['map-raw'] then table.insert(result, comma .. args['map-raw']) end if use=='basemap' then table.insert(result,']') end return table.concat(result) end --eg | minilocator=filename,bottom right,132px153px, 38%,60%, 22px local function makeLocatorMap (args, result) local miniFile,pos,itemlist,miniW,miniH, miniX,miniY,miniBox, miniBH if args['mini-locator'] then pos,miniFile=extractItem(args['mini-locator']) -- first item is filename, in quotes if it includes commas itemlist=splitItem(pos,5) -- put items in a table filename removed, position,WpxHpx, x%y%,box pos=itemlist[2] or 'right' miniW,miniH=getsize(itemlist[3]) miniX=tonumber(string.match(itemlist[4] or '0','[%d]+'))*miniW/100 miniY=tonumber(string.match(itemlist[5] or '0','[%d]+'))*miniH/100 miniBox=tonumber(string.match(itemlist[6] or '0','[%d]+')) miniBox=miniBox*miniW/100 miniBH=miniBox * maplist.height/maplist.width elseif args['mini-file'] then miniFile = args['mini-file'] pos=string.lower(args.minimap or 'right') if pos=='file' then pos='right' end miniW,miniH = tonumber(args['mini-width'] or 60), tonumber(args['mini-height'] or 60) -- find top left corner of locator miniBox,miniBH=tonumber(args['minimap-boxwidth'] or '0'),0 -- firm up pos offsets for dot (with % or not) and boxsize if any, miniX, miniY=args['minipog-gx'],args['minipog-gy'] if not miniX then miniX=tonumber(args['minipog-x'] or '0') else miniX=tonumber(miniX)*tonumber(miniW)/100 end if not miniY then miniY=tonumber(args['minipog-y'] or '0') else miniY=tonumber(miniY)*tonumber(miniH)/100 end if args['minipog-gx'] then miniBox=miniBox*miniW/100 end miniBH=miniBox * maplist.height/maplist.width else return end local miniTop,miniLeft=0,1 if not string.find(pos,'top') then --only use top left, as link box is in top right or put bottom left or right miniTop=maplist.height+2-miniH if string.find(pos,'right') then miniTop=miniTop-15 -- to avoid (c) line miniLeft=maplist.width-1-miniW end end table.insert(result,'<div style="position: absolute; outline-width: 1px; outline-style: solid; outline-color: white;') table.insert(result,'top: '..tostring(miniTop)..'px; left:'..tostring(miniLeft)..'px; width:'..tostring(miniW)..'px;' ) table.insert(result,'background-color:transparent; color:inherit;">[[File:'..miniFile..'|'..tostring(miniW)..'px|File:'..miniFile..']]</div>') if miniX and miniX>0 then if args['minipog-y'] then miniY=miniY/1.04 end if args['minipog-x'] then miniX=miniX/1.04 end if miniBox<1 then table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-3-6)..'px; left:'..tostring(miniX+miniLeft-3)..'px;') table.insert(result, 'width: 6px; background-color:transparent; color:inherit;">[[File:Red pog.svg|6px|link=]]</div>') else table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-miniBH/2)..'px; left:'..tostring(miniX+miniLeft-miniBox/2)..'px;') table.insert(result, 'width: '..tostring(miniBox)..'px; height:'..tostring(miniBH)..'px; outline:1px solid #AA1205; background-color:#AA120522; color:inherit;"></div>') end end end local function makeArcText(args,result,nval) local items, itemlist='',{} local arcText='' if args['arc'..nval] then items=convertCoords (args['arc'..nval]) items,arcText=extractItem(items) -- first item is text, in quotes if it includes commas itemlist=splitItem(items,9) -- put items in a table: text, lat,lon,size,color,angle,gap,radius,ellipse if itemlist[4]=='' then itemlist[4]='12' end itemlist[4]=string.match((itemlist[4] or '12'),"[%.%-?%d]+") if itemlist[5]=='' then itemlist[5]='grey' end itemlist[6]=string.match((itemlist[6] or '0'),"[%.%-?%d]+") itemlist[7]=string.match((itemlist[7] or '0'),"[%.%-?%d]+") itemlist[8]=string.match((itemlist[8] or '0'),"[%.%-?%d]+") end if args['arc-coord'..nval] then local itemTab=splitItem(convertCoordsTrad (args['arc-coord'..nval]),2) maplist.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) maplist.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) else maplist.lat=tonumber(string.match(args['arc-lat'..nval] or itemlist[2] or '0',"[%.%-?%d]+")) maplist.lon=tonumber(string.match(args['arc-lon'..nval] or itemlist[3] or '0',"[%.%-?%d]+")) end local arcX,arcY=maptogrid(maplist,6) arcText = args['arc-text'..nval] or arcText local fontSize =tonumber(args['arc-text-size'..nval] or itemlist[4] or '12') local textColor=getColor(string.gsub(args['arc-text-color'..nval] or itemlist[5] or 'grey','[%s]+','') ) local arcAngle= tonumber((args['arc-angle'..nval]) or (itemlist[6]) or '45')-90 local arcRadius =tonumber(args['arc-radius'..nval] or itemlist[8] or '0.05') local arcGap = tonumber(args['arc-gap'..nval] or itemlist[7] or '1')* ( ( math.sin(8-math.rad(arcRadius))^8 )+0.4 )*( ( fontSize+6 )/15 ) arcRadius=450*arcRadius*0.75 local ellipseFactor=tonumber(args['ellipse-factor'..nval] or itemlist[9] or '1') local arcRotate =arcAngle+90 if arcGap<0 then arcRotate=arcAngle-90 end local latF=arcY - fontSize + (0-(math.sin(math.rad(arcAngle))) * arcRadius) local lonF=arcX - fontSize +(0-(math.cos(math.rad(arcAngle))) * arcRadius) local step=1 for codepoint in mw.ustring.gcodepoint( arcText ) do -- block step=1,#arcText do table.insert(result,'<div style="position: absolute;') local posY=tostring(round( (latF + (math.sin(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius)) *ellipseFactor,2))..'px;' local posX=tostring(round( (lonF + (math.cos(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius))/ellipseFactor,2))..'px;' table.insert(result,' top: '..posY..' left: '..posX..' transform: rotate( '..tostring(round(arcRotate +((step-1)*arcGap)),2)..'deg);') table.insert(result,'width:'..tostring(fontSize*2)..'px; text-align: center; background-color:transparent; color: '..textColor..';') table.insert(result,'vertical-align: baseline; font-size: '..tostring(fontSize)..'px;">'..mw.ustring.char(codepoint)..'</div>') step=step+1 end end local function makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) local item={} itemdescription=stripdivs(itemdescription or '') local templon=lon if lon > 180 then templon=lon-360 end --for hemisphere+ or -1 dots if lon < -180 then templon=lon+360 end -- use 'real' coordinates for geohack label, while retaining shifted coords for plot if itemcolor=='transparent' then itemcolor='white' end itemcolor=getColor(itemcolor) -- ensure no opacity, which breaks maplink if string.find(itemcolor,'#')==1 and #itemcolor>7 then itemcolor=string.sub(itemcolor,1,7) end table.insert(item, '{ "type": "Feature", "properties": {') table.insert(item, ' "title": "'..itemtitle..'",') table.insert(item, ' "description": "'..itemdescription) table.insert(item, ' ([https://geohack.toolforge.org/geohack.php?params='..tostring(lat)..';'..tostring(templon)) table.insert(item, '_dim:2000 '..tostring(lat)..','..tostring(templon)..'])",') table.insert(item, ' "marker-symbol": "-number-'..string.gsub(group,'%W','')..'", "marker-size": "medium", "marker-color": "'..itemcolor..'" },') table.insert(item, ' "geometry": {"type": "Point", "coordinates": ['..tostring(lon)..','..tostring(lat)..'] } }') return table.concat(item) end local function makeLegendBox(result,args) local legend ={} local line,count, maxWidth='',1,8 local item line,item=extractItem(args.legendBox or '') local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end legend.Text = a -- convert hats to line breaks line=splitItem(line,6) -- (text, size, poition,background color, text/outline color, param options) legend.Size=line[2] or '150px80px1px' legend.Pos=line[3] or '10px10px' legend.Background=line[4] or 'beigeground' legend.Color=line[5] or 'darkbrown' legend.Param= line[6] or '' local argnum,legendCount,titleHeight='1',1,0 if (legend.Text and legend.Text~='') then titleHeight=15+(13.4*(select(2, string.gsub(legend.Text,"<br>", "")))) end local legendLine,legendGroup,legendY={},{},{} while args['legendItem'..argnum] do -- assign legendLine, legendGroup, legendY for each dot line,legendLine[legendCount] = extractItem(args['legendItem'..argnum] or '') line=splitItem(line,3) legendGroup[legendCount]=line[2] or argnum if shapeList[legendGroup[legendCount]] then maxWidth=math.max(tonumber(string.match(shapeList[legendGroup[legendCount]].shapeSize or '10','[%d]+')),maxWidth) else maxWidth=math.max(tonumber(string.match(shapeList['1'].shapeSize or '10','[%d]+')),maxWidth) end maxWidth=maxWidth+1 if line[3] then legendY[legendCount]=tonumber(string.match(line[3],'[%d]+')) else legendY[legendCount] = 3+maxWidth*(legendCount-1)+titleHeight -- if (legend.Text and legend.Text~='') then legendY[legendCount]=legendY[legendCount]+15 end end legendCount=legendCount+1 argnum=tostring(legendCount) end local w,h,r=getsize(legend.Size or '') local x,y=getsize(legend.Pos or '') local div=mw.html.create ('div') div:css('position', 'absolute') div:css('outline', '1px solid'..getColor(legend.Color)) if r~=0 then div:css('border-radius',tostring(r).."px") end div :css('top', y.."px") :css('left', x.."px") :css('width', w.."px") :css('height', h.."px") :css('line-height','105%') :css('background-color', getColor(legend.Background) ) :css('color','inherit') if not string.find(legend.Param,'noshadow') then div:css('box-shadow', '2px 2px 4px #33203335') end div:tag( 'div' ) :css('position', 'absolute') :css('top','1px') :css('left', (w/2).."px") :css('width',(w-8)..'px') :css('text-align', 'center') :css('color', getColor(legend.Color)) :css('transform', 'translateX(-50%)') :css('font-size','11px') :wikitext(legend.Text) :done() for lct=1,legendCount-1 do --local t=legendGroup[lct] local shape=shapeList[legendGroup[lct]] or shapeList['1'] local row={} row.gridx=3+maxWidth/2 row.gridy=(legendY[lct] or 0) + 5 if shape.shape=='image:' then row.gridy=row.gridy+6 end row.dx=0 row.dy=0 local legendShape= getshapetable(row,shape) div:wikitext(legendShape) :tag( 'div' ) :css('position', 'absolute') :css('top',(legendY[lct] or 0)..'px') :css('left', (maxWidth+6)..'px') :css('width', (w-maxWidth-6)..'px') :css('text-align', 'left') :css('line-height','103%') :css('color', getColor(legend.Color)) :css('font-size','10px') :wikitext(legendLine[lct]) :done() end div:allDone() table.insert(result,tostring(div)) end local function getQset (qValue,pValue,argNumber,args) local result={} local sortType=string.lower(args['geo-list-sort'] or 'a') local testValue='0' if sortType == 'alpha' or sortType == 'a' or sortType == 'ah' or sortType == 'al' then testValue = mw.wikibase.getLabel(qValue) elseif sortType == 'value' or sortType == 'v' or sortType == 'vh' or sortType == 'vl'then if qValue==nil or pValue == nil then debugmsg(mw.addWarning((qValue or 'nil')..' Qnumber for '..(pValue or 'nil'))) else if mw.wikibase.getBestStatements(qValue,pValue)[1] then testValue = mw.wikibase.getBestStatements(qValue,pValue)[1].mainsnak.datavalue.value.amount or '0' if pValue == 'P2046' then testValue = convertToSqKm(testValue, mw.wikibase.getBestStatements(qValue,pValue)[1].mainsnak.datavalue.value.unit) end end end elseif sortType == 'north' or sortType == 'n' or sortType == 'nh' or sortType == 'nl' then if mw.wikibase.getBestStatements(qValue,'P625')[1] then testValue = mw.wikibase.getBestStatements(qValue,'P625')[1].mainsnak.datavalue.value.latitude or 0 end elseif sortType == 'west' or sortType == 'w' or sortType == 'wh' or sortType == 'wl' then if mw.wikibase.getBestStatements(qValue,'P625')[1] then testValue = mw.wikibase.getBestStatements(qValue,'P625')[1].mainsnak.datavalue.value.longitude or 0 end end result[1]=testValue result[2]=argNumber return result end function p._main ( args ) local result={} local frame=mw.getCurrentFrame() local dotTable={} local magVal,scaleVal = args.magnify,'' local origH,origW=maplist.height,maplist.width if magVal then --set up the values needed to magnify the top-right portion of the map magVal=tonumber(string.match(magVal or '0',"[%.%-?%d]+")) or 1 if magVal>1 and magVal <=2 then maplist.height= round(maplist.height/magVal,0) maplist.width=round(maplist.width/magVal,0) scaleVal='transform: scale('..magVal..') translateY('..tostring((origH-maplist.height)/2)..'px);' else magVal=1 end end -- set up the three nested div boxes (plus an extra if centered) to put the map plus title/caption area, in an appropriate frame on the page if args.float=='center' or args.float=='centre' then table.insert(result,'<div class="center"><div class="thumb tnone">') elseif args.float=='left' then table.insert(result,'<div class="thumb floatleft">') else table.insert(result,'<div class="thumb floatright">') end table.insert(result,'<div class="thumbinner" style="position: relative; top: 0px; right: 0px; width: '..(args.width or "400")..'px;">') if args.title then table.insert(result,'<div class="center" style="font-weight:bold">'..args.title..'</div>') end if magVal and magVal >1 and magVal<=2 then table.insert(result,'<div class="thumbinner noresize" style="display:block; position: relative; outline:0px; border:0px; padding:0px; background-color:transparent; color:inherit;') table.insert(result,'top: 50%; right: '..tostring(0-((origW-maplist.width))/2)..'px; ') table.insert(result,'height: '..origH..'px; width: '..maplist.width..'px; '..scaleVal..'">') else magVal=1 table.insert(result,'<div class="thumbinner noresize" style="position: relative; outline:0px; border:0px; padding:0px;') table.insert(result,'top: 0px; right: 0px; ') table.insert(result,'height: '..maplist.height..'px; width: '..maplist.width..'px ">') end --add any boundary Qvalues to map-data-light if requested if args['geo-region'] and (args['geo-boundaries'] == '1') then local qmaps=args['map-data-light'] or '' if qmaps ~='' and string.sub(qmaps,#qmaps) ~= ',' then qmaps=qmaps..',' end local geoRegion = args['geo-region'] -- or add base-page here local geoSubRegion = args['geo-sub-region'] or 'P150' if not mw.wikibase.isValidEntityId(geoSubRegion) then geoSubRegion = 'P150' end if mw.wikibase.isValidEntityId(geoRegion) then local ct=1 local qtemp while (mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct]) do -- debugmsg('table '..ct..' - '..mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id) qtemp = mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id or nil if qtemp then if mw.wikibase.entityExists(qtemp) then qmaps=qmaps..qtemp..',' -- see if there is a geocode boundary Qvalue to add else debugmsg(mw.addWarning('unable to add geocode value for '..mw.wikibase.getLabel..','..qtemp)) end end ct=ct+1 end end if qmaps and qmaps ~= '' then -- add back the created/expanded list of boundary Qvalues, and remove trailing comma args['map-data-light'] = string.sub(qmaps,1,#qmaps-1) end end -- Create the basemap using mapframe local mapframecontent=getmapframecontent(args,'basemap') table.insert(result, frame:extensionTag{ name ='mapframe', content=mapframecontent, args={width=tostring(maplist.width), height=tostring(maplist.height), zoom=tostring(maplist.zoom), longitude=tostring(maplist.lonbase), latitude=tostring(maplist.latbase), mapstyle=maplist.mapstyle, frameless=true } } ) --Add coverall box to block the unhelpful links from mapframe - which wouldn't include all the dots. Reinstate some links to osm and wikimedia table.insert(result,'<div style="position: absolute;width:'..tostring(maplist.width)..'px; height:'..tostring(maplist.height)..'px;') table.insert(result,'top:0px;left:0px;background-color:#FFFFFF00; color:inherit;"></div>') --Add replacent hover-links for OpenStreetMap and maps terms and conditions table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-13)..'px; width: 12px; height: 12px">') table.insert(result,'[[file:Transparent.svg|12px|link=https://www.openstreetmap.org/copyright|'..aboutOSM..']]</div>') table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-110)..'px;') table.insert(result,'width: 12px; height: 12px background-color: transparent; color:inherit;">') table.insert(result,'[[file:Transparent.svg|12px| link=https://foundation.wikimedia.org/wiki/Policy:Maps_Terms_of_Use|'..termsOfUse..']]</div>') -- Add scale-line if not args.scalemark or args.scalemark~='0' then local top=maplist.height-42 local left=maplist.width-61-(tonumber(args.scalemark or '1')) local minipos=string.lower(args.minimap or '') -- scalemark gets pushed left if it would be behind the minimap if minipos~='' and not(string.find(minipos,'left') or string.find(minipos,'top') ) then local offset=tonumber(args.scalemark or '1')-tonumber(args['mini-width'] or '60') if offset<1 then left=maplist.width-61-tonumber(args['mini-width'] or '60') end end if maplist.width-left >216 then top=top+14 end -- shunt scaleline down if it is beyond the copyright stuff local scalek,scalem=getScale(maplist.zoom, maplist.latbase, magVal) local magReduce='' table.insert(result,"<div style=\"display:inline-block; position: absolute; background-color: #111111") table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;M0,8 l0,4 l20,0 l0,-4 l-0.3,0 l0,3.7 l-19.4,0 l0,-3.7 z&#39;) ") table.insert(result,"; width:20px") -- path is a 20px20px box, and then rescales if magVal==1 then table.insert(result,"; top:"..tostring(top-1).."px") table.insert(result,"; left:"..tostring(left+16).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5)..", "..tostring(1.5)..")") else -- shrink the scalemark to compensate for magnification table.insert(result,"; top:"..tostring(top-(magVal*0.28)).."px") table.insert(result,"; left:"..tostring(left+(16*(magVal*1.15))).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5*(1/magVal))..", "..tostring(1.5*(1/magVal))..")") magReduce= 'scale('..tostring(1/magVal)..')' end table.insert(result,"\"></div>") table.insert(result,'<div style="position: absolute; top: '..tostring(top)..'px; left: '..tostring(left+47)..'px; font-size: 9.5px; line-height: 126%; width: fit-content;') table.insert(result,'color: #444433; background-color: transparent; text-align: right; transform: '..magReduce..' translateX(-100%);">'..scalek..'<br>'..scalem..'</div>') end --Set up the shapeList and dotList tables, to provide data to go on the map local sgNumbers,sgSortable={},{} --s1,s2 sgNumbers["1"]="1" if args.useFormatStyle and args.useFormatStyle=='shortstyle' then shapeList=ParseShapeTypes (shapeList,args,"1") for argindex,argv in pairs(args) do -- build a list of all the numbered sg's that have been used if string.find(argindex,"sg[a-f,n%d]+") == 1 then -- only look through the sga,sgb, sgc,sgd,sge,sgf and sgn args local x=string.match(argindex,"[%d]+") -- find its number if x and not sgNumbers[x] then sgNumbers[x]=x end -- only add if not already found end if string.find(argindex,"sg[a-f]H") == 1 and highlightNum then highlightOption=true end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form for k,v in pairs(sgSortable) do -- work through the sorted list, parsing each set of sg's in turn, from 1 upwards shapeList=ParseShapeTypes (shapeList,args,v) end if highlightOption==true then shapeList=ParseShapeTypes (shapeList,args,'H') end end local dotList,dotresult,dotItemTable,dotGroupList={},{},{},{} if (not args.useFormatStyle) or args.useFormatStyle=='standardstyle' then local dotmax=0 local qset={} for indx,val in pairs(args) do if string.match(indx,'mark%-coord[%d]+') or string.match(indx,'lat[%d]+') or string.match(indx,'geo%-data[%d]+') then dotmax=math.max(dotmax, tonumber(string.match(indx,"[%d]+"))) if string.match(indx,'geo%-data[%d]+') then local argnum=string.sub(indx,9,#indx) or '' if args['mark-title'..argnum] == 'none' or (string.find(args['mark-title'..argnum] or 'x','nolist')== 1) then -- debugmsg('sort values ignored '..argnum..', '..args['mark-title'..argnum]) else qset[val]=getQset (val, (args['geo-data-type'] or 'P1082'), argnum, args) end end end if (indx=='shapeH') or (indx=='shape-colorH') or (indx=='shape-outlineH') or (indx=='label-colorH') then highlightOption=true end end local qlist=args['geo-data-list'] if qlist then if string.sub(qlist,#qlist) ~= ',' then qlist=qlist..',' end else qlist='' end local geoRegion = args['geo-region'] -- or add base-page here local geoSubRegion = args['geo-sub-region'] or 'P150' if not mw.wikibase.isValidEntityId(geoSubRegion) then geoSubRegion = 'P150' end if (args['find-geo-dots'] or '1') ~= '0' and geoRegion and mw.wikibase.isValidEntityId(geoRegion) then local ct=1 local qtemp while (mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct]) do -- debugmsg('table '..ct..' - '..mw.wikibase.getBestStatements(adminRegion,adminSubRegion)[ct].mainsnak.datavalue.value.id) qtemp = mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id or nil if qtemp then qlist=qlist..qtemp..',' -- add subregion Qvalue to the list end ct=ct+1 end end if qlist and #qlist>1 then -- create a table from the csv list local _, c = qlist:gsub(",","") local qtab=splitItem(qlist,c) for xd,yd in pairs(qtab) do yd=string.upper(yd) if qset[yd] == nil and mw.wikibase.isValidEntityId(yd) then dotmax=dotmax+1 qset[yd]=getQset (yd, (args['geo-data-type'] or 'P1082'), tostring(dotmax), args) args['geo-data'..tostring(dotmax)] = yd -- debugmsg(dotmax..' = '..(string.upper(yd) or 'Q Not found')..': '..(mw.wikibase.getLabel(yd) or 'label Not found')..': '..(mw.wikibase.getSitelink(yd) or 'link Not found')) elseif not mw.wikibase.isValidEntityId(yd) then debugmsg(mw.addWarning(yd..' is not a [[wikidata]] page. geo-data-list needs comma-separated Qvalues' )) else debugmsg(mw.addWarning(yd..' is duplicate')) end end end -- qset contains set of Qvalues and can be from wikidata geo-region, list of geo-data-list Q values or individual geo-data1= etc local ts = {} -- table to hold the keys local useQset=false for ky, val in pairs(qset) do table.insert(ts, {key = ky, value = val[1], num=val[2]}) useQset=true end if useQset then local order = string.lower(args['geo-list-sort'] or 'a') -- sort the list from previously stored sortkey, either Lowest or Highest first if string.find(',al,vl,nl,wl,west,w,alpha,a,' , ','..order..',') then table.sort(ts, function (a,b) return lessthan(a.value, b.value) end) else table.sort(ts, function (a,b) return morethan(a.value, b.value) end) end for ky,val in ipairs(ts) do -- assign values from sorted list to numbered parameter args['numbered'..val.num] = ky -- The numbered parameter becomes the value for each dot that has a geo Qvalue, from manual, list or wiki sources. end end dotItemTable=tradstyleParseShapes(args,dotItemTable,dotmax) for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) or args['geo-data'..x] then sgNumbers[x]=x -- add it to the list end end else for indx in pairs(args) do if string.match(indx,'dot[%d]+') then table.insert(dotList,indx) end --add the index name for dot1, dot2 etc to dotList end table.sort(dotList,morethan) for indx,dotName in pairs(dotList) do dotresult=ParseData(args,string.match(dotName,'[%d]+') ) -- using each dot number, assign the settings for each dot to a dotresult item line table.insert(dotItemTable,dotresult) -- and store that item line within the dotItemTable end end for arcVal = 65,91 do -- check through args looking for any arcs local arcLetter=string.char(arcVal) if args['arc-text'..arcLetter] or args['arc'..arcLetter] then makeArcText(args,result,arcLetter) end end local dotdivs='' local ddots=0 if dotItemTable[1] then local ddots=(dotItemTable[1].lat or 0)+(dotItemTable[1].lon or 0) end local fgroup='F'..tostring(maplist.latbase+maplist.lonbase+ddots ) local FullscreenList={} local addcomma='' for i,dotitem in pairs(dotItemTable) do -- working throug each dot item, merge the dot and shape values into a full set of css text local dotgroup= dotitem.group or "0" if dotitem.posType=='mark-line' and dotitem.markDest then --find destination xy values for any mark-lines for n,v in pairs(dotItemTable) do if v.code == dotitem.markDest then dotitem.gridx2=v.gridx dotitem.gridy2=v.gridy break end end end local qtype=dotitem.group -- find which shape group each dot has been assigned --debugmsg('dotgroup='..qtype..', sg='..(sgNumbers[qtype] or 'nil')..' , shapeList='..shapeList[qtype].shape) if not sgNumbers[qtype] then qtype="0" end --shapeList[dotitem.group] will give access to the shape values for that dot if highlightNum==dotitem.code and highlightOption==true and shapeList['H'] then table.insert(result, getshapetable(dotitem,shapeList['H'])) else table.insert(result, getshapetable(dotitem,shapeList[qtype])) -- Add the actual css instructions for each dot end if shapeList[dotgroup] and not string.find((dotitem.param1 or ''),'nomap') then -- only add if not excluded with 'nomap' labelText local ftext='' if dotitem.dotTag~='' and not string.match(dotitem.labelText or '','[%d]') then ftext=stripdivs(dotitem.dotTag or '')..' <br>' end if (dotitem.labelText ~= ftext) and dotitem.dotLink =='' then ftext=ftext..' '..stripdivs(dotitem.labelText)..'<br>' end if (dotitem.dotLink) and (dotitem.dotLink ~='') then ftext=ftext..dotitem.dotLink..'<br>' end if dotitem.imageName then ftext=ftext..'[[File:'..dotitem.imageName..'|250px]]' end table.insert(FullscreenList,1, makeFullscreenItem (string.gsub(ftext,"[\n]+"," "), dotitem.info,round(dotitem.lat,5),round(dotitem.lon,5),fgroup,shapeList[dotgroup].shapeColor)..addcomma ) addcomma=', ' end -- makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) -- Always add to start of list, to reverse the sequence, and separate with commas except for first item, which is now at the end end if args.legendBox then makeLegendBox(result,args) end if args.minimap or args['mini-locator'] then makeLocatorMap(args,result) end -- add tag link and details for fullscreen version addcomma='' if (mapframecontent or '[]') ~= '[]' then addcomma=',' end mapframecontent=getmapframecontent(args,'fullscreen') local contentstart='[ '..mapframecontent..addcomma..'{ "type": "FeatureCollection", "features": [ ' --extra features after first square bracket local contentend=' ] } ]' table.insert(result, '<div style="position: absolute;top: 9px;left: '..tostring(maplist.width-34)..'px">') table.insert(result, '<div style="color: white; opacity:100; font-size: 19px; font-weight:normal; text-align: left;">') table.insert(result, frame:extensionTag{ name ='maplink', content=contentstart..table.concat(FullscreenList)..contentend, args={zoom=tostring(maplist.zoom+1), class='no-icon', frameless='1', latitude=tostring(maplist.latbase), longitude=tostring(maplist.lonbase), --add invisble 'en-spaces' for tooltip text='<div title="'..fullscreenlinktext..'">&nbsp;&nbsp;&nbsp;</div>'} } ) table.insert(result,'</div></div>') -- end of maplink ----- -- add closing div for main map table.insert(result,'</div>') -- collate caption material to go in the outer div class if captionTable ~= '' and (not args['auto-caption']) then args['auto-caption']='on' end --default to include list if using geo values local autocaption=string.lower(args['auto-caption'] or 'no') local autoOff=autocaption:match("(%w+)(.*)") -- select the first word in autocaption and see if it is a negativeAnswer) if negativeAnswer[autoOff] then captionTable='' end if args.caption or (not negativeAnswer[autoOff]) then table.insert(result,'<div class="thumbcaption" style="text-align:left">') if args.caption then table.insert(result,args.caption) if captionTable ~= '' then table.insert(result,'<hr>') end end end local columns=tonumber(autoOff:match("[%d]+") or '1') if columns>1 then columns=round(maplist.width/(columns*17), 0) end -- convert from em to px for historical reasons --for k in pairs(dotList) do capchk=capchk..(args["dotlink"..k] or '') end local capchk=nil local captionList = {} for key, value in pairs(dotItemTable) do -- only add an autocaption line if there is both a dotTag and a dotLink line available and it is not marked as nolist if value.dotTag and value.dotTag~='' and (not string.find(value.param1 or '','nolist')) and string.gsub(value.dotLink or '',"%s+","")~='' then table.insert(captionList, {key = key, value = value}) if captionTable == '' then capchk=true else -- add the next line of content to the html table capchk=false end end end if captionTable ~= '' then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) for k,v in pairs(captionList) do local nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag local ngrp=v.value.group if captionTableText[ngrp] then -- debugmsg('table text for'..v.value.dotTag..', '..captionTableText[v.value.group][1]..', '..captionTableText[v.value.group][2]..', '..captionTableText[v.value.group][3]..' ('..captionTableText[v.value.group][4]..')') local c1,c2 c1,c2=checkColors(shapeList[ngrp].shapeColor) local cDot='<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;' cDot=cDot..'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div>' captionTable = htmlTableLine(captionTable,cDot,captionTableText[ngrp][1], captionTableText[ngrp][2], captionTableText[ngrp][3], captionTableText[ngrp][4]) end end end if capchk and (not negativeAnswer[autoOff]) then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) -- local myDivision = string.gsub((args.toggletext or toggletext), "%s+", "") if string.find(autocaption,'collaps') then table.insert(result,'<div class = "wikitable mw-collapsible ') if string.find(autocaption,'collapsed') then table.insert(result,'mw-collapsed') end table.insert(result,'" style="width:100%; line-height: 17px; border-style:none; text-align: center"><b>'..(args.toggletext or toggletext)..'</b>') end if string.find(autocaption, 'columns=') then columns=string.match(autocaption,'[%d]+',string.find(autocaption, 'columns=') ) end table.insert(result,'<div class="mw-collapsible-content" style="column-count:'..columns..'; column-rule:solid 1px;text-align:left;padding-top:5px">') table.sort(dotList,lessthan) local nval,ngrp='','0' for k,v in pairs(captionList) do nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag ngrp=v.value.group or '0' if v.value.dotLink and v.value.dotLink~='' and nval and nval~='' then local c1,c2 if nval==args.highlight then c1,c2=checkColors(shapeList['H'].shapeColor) else c1,c2=checkColors(shapeList[ngrp].shapeColor) end table.insert(result,'<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;') table.insert(result,'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div> '..v.value.dotLink..'<br>') end end table.insert(result,'</div>') -- end for caption-content div if string.find(autocaption,'collaps') then table.insert(result,'</div>') end -- end for toggle frame end if captionTable ~= '' then table.insert(result,captionTable..'</table>') end if args.caption or (not negativeAnswer[autoOff]) then table.insert(result,'</div>') end -- end for whole caption frame table.insert(result,'</div></div>') -- outer two frames if args.float == 'center' or args.float=='centre' then table.insert(result,'</div>') end if args['show-new-format'] == 'hints' then -- provide a 'format hint panel' in the 'Preview Box' local w="<small>Below are some template hints for the 'sga' compressed version, " w=w..'{{tl|OSM Location dots}}. It can use these, instead of the more verbose {{tl|OSM Location map}} parameter format. ' w=w..'Data is divided between a "ShapeGroup" and the "Dots", so that a single shapeGroup can be used for multiple dots on the map. ' previewMsg(w..'(nb. the "group" value can be the number or an assigned name of a shapeGroup)</small>') w='<small>{{tl|OSM Location dots}}: | dot(n)=group,lat,lon,dotTag | ' w=w..'dotlink=link/tooltip | dotlabel=label,position,dx,dy,param1,info | dotpic=filename <br>' w=w.."(nb. param1 options include 'nolink' 'nolist' 'nomap' 'hemisphere-1' 'hemisphere+1', 'noline' - quotes not required, separate with spaces).<br>" w=w..'| sga = Shape,Sizepx,Color,Angledeg | sgb= OutlineWidth,Color,Style | sgc=TextSize,Color,Angle,bold italic <br>' w=w..'| sgd=TagSizepx,Color,Spacer,Angledeg | sge=LineWidth,Color,Style | sgf=TextSpacingpx,LineHeight%,Outlinepx,backgroundColor<br>' w=w..'| sgn=Name (optional, to assign a meaningful name) | sgp=Parent (can be the name or number of the parent shapeGroup. ' previewMsg(w..'Each shapegroup will inherit values from a parent, stretching back to "sga1" and its default values.)</small>') if #pmsg then local dbg={} for i,x in pairs(pmsg) do table.insert(dbg,x..'<br>') end dbg=mw.addWarning(table.concat(dbg)) table.insert(result, dbg) end end if args.coordtest then debugmsg(mw.text.nowiki(args.coordtest)) end for i,x in pairs(msg) do table.insert(result, x..'<br>') end return table.concat(result) end function p.main(frame) local args = getArgs(frame) local itemTab={} maplist.width=tonumber(args.width) or 400 maplist.height=tonumber(args.height) or 300 if args.coord then itemTab=splitItem(convertCoordsTrad (args.coord),2) maplist.latbase=itemTab[1] maplist.lonbase=itemTab[2] else maplist.lonbase=tonumber(args.lon) or 5 maplist.latbase=tonumber(args.lat) or 0 end maplist.zoom=tonumber(args.zoom) or 1 visibleLinks=args.showlinks highlightNum=args.highlight if args.nolabels=='1' then maplist.mapstyle='osm' else maplist.mapstyle='osm-intl' end if maplist.width >= 400 and not(args['show-q-values']) then args['show-q-values'] = '1' -- default to on if wider than 400px elseif maplist.width < 320 then args['show-q-values'] = '0' end -- turn off Qvalus if frame is too narrow, even if requested return p._main(args) end return p ab3tfenupaard7gfiwsjw3cdm7zqtxd 204452 204451 2026-07-05T14:42:03Z HaxelKaszëba 17492 204452 Scribunto text/plain require('strict') local delink=require('Module:Delink').delink local getArgs = require('Module:Arguments').getArgs local p = {} local maplist={} local sgNames={} local highlightOption=false local highlightNum local visibleLinks local geoTotalPop = 0 local captionTable = '' local captionTableText = {} -- This module creates framed maps of anywhere in the world, at the required scale, and enables annotations, -- dots, shapes, lines and other ways to customise the area of the map being shown. It also provides a link -- to an interactive fullscreen version, which has locator dots instead of annotations and shapes. -- This is the 2025 successor module to a wiki-markup template version of 2024, which itself was a successor -- to the 'Graph'/VEGA driven template that was begun in 2016, until the Vega version was switched off in 2023. -- This module is called from template {{OSM Location map}}, which uses the same parameter formats as before. -- In addition it may at some point be possible to use a more concise parameter format using the template {{OSM Location dots}} -- In general the css output from the two formats will be identical, but the the concise version will allow bits of -- greater control over some of the settings. -- see the documentation on the two template pages for details of how to use the mapping features. -- If language customisation is needed, there are text items below that can be translated. Also see the color table -- below with details of how to add additional color names to allow localised alternatives. -- (Translating other language shape-types could be possible, but has not currently been contemplated. -- Parameter name translation would be harder but likely to be possible, ideally still retaining compatibility -- with template calls already written using English). -- -------------- the following text variables are used within the template, and can be translated as required --------------- -- -------------- you may wish to retain the English version as a comment, to keep track of the original phrase -------------- local negativeAnswer={no=1,'0'-1,off=1} local fullscreenlinktext = 'Click for interactive fullscreen map with links to nearby articles' local toggletext = 'List of numbered map items:' local termsOfUse = 'Maps: terms of use' local aboutOSM = 'About OpenStreetMaps' local yards = 'yds' local miles = 'miles' local metres = 'm' local km = 'km' local sqkm = 'sq.km' local year = 'year' local notFound = 'not found' local totalItems = 'Total' local sumOfDatapoints = 'sum of datapoints' local numberCol = 'No.' -- abbreviated for top of column of code numbers local wikidataLink = 'Wikidata page' local shapeList={} --This sets up the 'factoryDefault' shape group 0 shapeList["0"]={shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border of the shape outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items text settings for labels textSZ="11px", textCL="darkgrey", textNG="0deg", --sgf further text settings textSP="0px", textLH="120%", textOL="0px", textBG="transparent", --sgd items for dotTag text settings tagSize="10px", tagColor="white", tagSpacer="0px", tagAngle="0deg", --sge items for extension line to connect label to dot textEW="0px", textEC="darkgrey", textES="solid" } local colorList={} -- used by colorLookup to catch unsupported colors (eg 'LimeGreen'), to convert to generic version colorList['green']='hardgreen' -- it could also be added to to include alternative language equivelants, for a quick solution. colorList['red']='hardred' -- colorList ['source'] = target colorList['white']='white' -- converts any color that includes 'source' into its equivelent target colorList['blue']='hardblue' -- note, for translation you can add to this list, rather than replace it, colorList['brown']='brown' -- which would mean existing map definitions in english would also still work, alongside translated ones colorList['grey']='hardgrey' colorList['gray']='hardgrey' colorList['purple']='hardpurple' colorList['orange']='hardorange' colorList['leaf']='hardleaf' --for a more thorough translation, you can add all the variants of the colors as further CTB elements and hex values or redirects local CTB={} -- set up a table of color names (the CTB Color table index) and html hash colorhex values. CTB["paleblue"],CTB["softblue"],CTB["hardblue"],CTB["darkblue"]="#D6E1EC","#77A1CB","#4B77D6","#1c559e" CTB["palered"],CTB["softred"],CTB["hardred"],CTB["darkred"] = "#FCC6C0","#EC644B","#DB3123","#AA1205" CTB["palegreen"],CTB["softgreen"],CTB["hardgreen"],CTB["darkgreen"]= "#D2F0E5","#81AF81","#269F46","#0b7527" CTB["paleleaf"],CTB["softleaf"],CTB["hardleaf"],CTB["darkleaf"]= "#dff5c1","#b5e376","#8cc244","#679c21" CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"]= "#E8E8D6","#AAAA88","#777755","#444433" CTB["palegray"],CTB["softgray"],CTB["hardgray"],CTB["darkgray"]=CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"] CTB["palebrown"],CTB["softbrown"],CTB["hardbrown"],CTB["darkbrown"]="#FAF6ED","#CCB56C","#AD7F14","#754910" CTB["palepurple"],CTB["softpurple"],CTB["hardpurple"],CTB["darkpurple"]="#e0d1e6","#c784e0","#a029cf","#7a05a8" CTB["paleorange"],CTB["softorange"],CTB["hardorange"],CTB["darkorange"]="#ffedc2","#ffcf61","#EEB533","#e39f05" CTB["black"],CTB["white"],CTB["yellow"]="#000000","#FFFFFF","#FAF039" CTB["background"],CTB["paleground"],CTB["beigeground"]="#f9f6f2","#FEFEFA","#F5F5DC" CTB["beige"]=CTB["beigeground"] CTB["aqua"],CTB["teal"],CTB["fuchsia"] = "#00FFFF","#008080","#FF00FF" CTB["maroon"],CTB["olive"],CTB["navy"] = "#800000","#808000","#000080" CTB["lime"],CTB["limegreen"],CTB["aquamarine"] = "#00FF00","#32CD32","#7FFFD4" CTB["silver"],CTB["yellow"],CTB["orchid"] = "#800000","#FFFF00","#DA70D6" -- set up a table of predefined clip-paths local pathshape={} pathshape.squaredd = "M 19,1.25 l 0,18 -18,0 0,-18 18,0m-1,1 -16,0 0,16 16,0 0,-16m-1,1 0,14 -14,0 0,-14 14,0zm-1,1 -12,0 0,12 12,0 0,-12zm-1,1 0,10 -10,0 0,-10 10,0z" pathshape.squared = "M 18,2.5 l 0,15 -15,0 0,-15 15,0m-1,1 -13,0 0,13 13,0 0,-13zm-1,1 0,11 -11,0 0,-11 11,0z" pathshape.triangledd="M 0 20,20 20,10 0,0 20ZM1.5 19,10 1.7,18.5 19,1.5 19ZM3 18,17 18,10 3.8,3 18ZM4.5 17,10 5.4,15.4 17, 4.5,17ZM6 16,13.8 16,10 7.4z" pathshape.triangled ="M1,18 l 18,0 l -9,-18 l -9,18zm1.7,-1.1 l 7.3,-14.6 l 7.3,14.6 l -14.6, 0zm1.7,-1 l 11.0,0 l -5.5,-11 l -5.5,11z" pathshape.circledd = "M0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm0.8,0a9.2,9.2 0 1,1 18.4,0a9.2,9.2 0 1,1 -18.4,0m1,0a8.2,8.2 0 1,0 16.4,0a8.2,8.2 0 1,0 -16.4,0zm0.8,0a7.2,7.2 0 1,1 14.8,0a7.2,7.2 0 1,1 -14.8,0m1,0 a6.4,6.4 0 1,1 12.8,0a6.4,6.4 0 1,1 -12.8,0z" pathshape.circled = "M2.5,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0m0.8,0 a5,5 0 1,1 11.4,0a5,5 0 1,1 -11.4,0" pathshape.diamond = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10z" pathshape.diamondd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm1,0 l 6,8.5 l 6,-8.5 -6,-8.5 -6,8.5zm1,0 l 5,-7 5,7 -5,7 -5,-7z" pathshape.diamonddd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm0.75,0 l 6.25,9 l 6.25,-9 -6.25,-9 -6.25,9zm0.75,0 l 5.5,-8 5.5,8 -5.5,8 -5.5,-8zm0.75,0 l 4.75,7 l 4.75,-7 -4.75,-7 -4.75,7zm0.75,0 l 4,-6 4,6 -4,6 -4,-6z" pathshape.crossd = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0zM2.3,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.cross = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0z" pathshape.thincross = "M2,12 l6,0 l0,6 l4,0 l0,-6 l6,0 l0,-4 l-6,0 l0,-6 l-4,0 l0,6 l-6,0z" pathshape.fivepointstar = "M10 0 L12.245 6.91 19.511 6.91 13.633 11.18 15.878 18.09 10 13.82 4.122 18.09 6.367 11.18 0.489 6.91 7.755 6.91Z" pathshape.fivepointstard = "M10 1.5 L 11.90825 7.3735 18.08435 7.3735 13.08805 11.003 14.9963 16.8765 10 13.247 5.0037 16.8765 6.91195 11.003 1.91565 7.3735 8.09175 7.3735 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sixpointstar = "M10 0 L12.323 5.977 18.66 5 14.645 10 18.66 15 12.323 14.023 10 20 7.677 14.023 1.34 15 5.355 10 1.34 5 7.677 5.977Z" pathshape.sixpointstard = "M10 1.5 L 11.97455 6.58045 17.361 5.75 13.94825 10 17.361 14.25 11.97455 13.41955 10 18.5 8.02545 13.41955 2.639 14.25 6.05175 10 2.639 5.75 8.02545 6.58045 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sevenpointstar = "M10 0 L12.048 5.747 17.818 3.765 14.602 8.95 19.749 12.225 13.69 12.943 14.339 19.01 10 14.72 5.661 19.01 6.31 12.943 0.251 12.225 5.398 8.95 2.182 3.765 7.952 5.747Z" pathshape.sevenpointstard = "M10 1.5 L11.7408 6.38495 16.6453 4.70025 13.9117 9.1075 18.28665 11.89125 13.1365 12.50155 13.68815 17.6585 10 14.012 6.31185 17.6585 6.8635 12.50155 1.71335 11.89125 6.0883 9.1075 3.3547 4.70025 8.2592 6.38495 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.eightpointstar = "M10 0 L11.88 5.46 17.071 2.929 14.54 8.12 20 10 14.54 11.88 17.071 17.071 11.88 14.54 10 20 8.12 14.54 2.929 17.071 5.46 11.88 0 10 5.46 8.12 2.929 2.929 8.12 5.46Z" pathshape.eightpointstard = "M10 0 L10 1.5 L11.598 6.141 16.01035 3.98965 13.859 8.402 18.5 10 13.859 11.598 16.01035 16.01035 11.598 13.859 10 18.5 8.402 13.859 3.98965 16.01035 6.141 11.598 1.5 10 6.141 8.402 3.98965 3.98965 8.402 6.141ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.ring="M2.6,9.5a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.semicircle = "M0,5 C0,18.3,20,18.3,20,5 L 0,5z" pathshape.arrowhead = "M 10,10.2 l 10,2.8 -2,-3 2,-3 -10,2.8 -0.2,0.2z" pathshape.boxd=pathshape.squared pathshape.boxdd=pathshape.squaredd pathshape.ellipsed=pathshape.circled pathshape.ellipsedd=pathshape.circledd local msg={} local function debugmsg(txt) table.insert(msg,txt) end local pmsg={} local function previewMsg(txt) table.insert(pmsg,txt) end local function colorLookup(color) for c,l in pairs(colorList) do if string.find(color,c) then return l end end return color end local function getColor (color,chk) local c local opacity="100" if not color or color=='' then color='hardgrey' end if color=="transparent" then return color end if color=="background1" then color='background' end if string.byte(color,1,1)==35 and (#color == 7 or #color == 9) then c=color elseif string.byte(color,1,1)==35 and #color == 4 then c=string.sub(color,1,2)..'f'..string.sub(color,3,3)..'f'..string.sub(color,4,4)..'f' else local s=color..'1' s= s:sub(0,s:find("%d")-1) opacity=string.match(color,"%d+") if not CTB[s] then s= colorList[s] end -- check for synonyms and translations if not CTB[s] then debugmsg(mw.addWarning('color = '..color..'. The color name is not defined. Used default grey instead')) end c=CTB[s] or CTB.hardgrey end if opacity and (tonumber(opacity) < 100) and string.find(c,"#")==1 and string.len(c)==7 and opacity~="" then local hexval=string.format("%x",(math.floor(tonumber(opacity)*2.55))) c=c..hexval end return c end function p.colorvalue(frame) -- enable external access to the CTB colorTable values. usage: {{#invoke:OSM Location map|colorvalue|color=hard blue}} local c if not frame.args.color or frame.args.color=='' then c='grey' else c=string.lower(string.gsub(frame.args.color,'%s+','')) end return string.upper(string.sub(getColor(c),2)) end local function checkColors(color) local c=getColor(color,'check') local opacity =1 -- calculate colour brightness and return black or white for contrast if c=='transparent' then return c,'#000000',0 end if not (string.find(c,'#')==1) then return c,'#FFFFFF',0 end if #c>8 then opacity= tonumber('0x'..(string.sub(c,8,9)))/500 end local r=tonumber('0x'..(string.sub(c,2,3)))/255 local g=tonumber('0x'..(string.sub(c,4,5)))/255 local b=tonumber('0x'..(string.sub(c,6,7)))/255 if 0.2126 * r + 0.7152 * g + 0.0722 * b / opacity < 0.7 then return c,'#FFFFFF',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity else return c,'#000000',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity end end local function morethan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) and tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%-?%f[%d]%d[,.%d]*%f[%D]') ) b = tonumber(string.match(b, '%-?%f[%d]%d[,.%d]*%f[%D]') or '0') end return a>b end local function lessthan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) and tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%-?%f[%d]%d[,.%d]*%f[%D]')) b = tonumber(string.match(b, '%-?%f[%d]%d[,.%d]*%f[%D]') or '0') end return a<b end local function getsize(size) --size1 is between 1 and 3 values, each with px, equating to width,height,corner-rounding --eg '15px 25px 5px' (spaces are optional) or '18px'. returns three numbers local sizeval = {} for v in string.gmatch(size, "[^px]+") do table.insert(sizeval,v) end sizeval[1] = tonumber(sizeval[1]) or 13 sizeval[2] = tonumber(sizeval[2]) or sizeval[1] sizeval[3] = tonumber(sizeval[3]) or 0 return sizeval[1],sizeval[2],sizeval[3] end local function coord2text(coord) -- looks through the output from {{coord}} to find the lat and long decimal values -- and converts compass points to minus or not-minus, return with separating comma. local lat = string.match(coord,'[%.%d]+°[NS]') local lon = string.match(coord,'[%.%d]+°[EW]') local neg={N="",S="-",W="-",E=""} return neg[string.match(lat, '[NS]')]..string.match(lat,'[%.%d]+')..","..neg[string.match(lon, '[EW]')]..string.match(lon,'[%.%d]+') end local function convertCoordsTrad (row) local coords='' if row and string.find (row,'<span class="geo">') then local a,b=string.find(row,'<span class="geo">') local start=b+1 a,b=string.find(row,"</span>",b) local finish=a-1 coords=string.sub(row,start,finish) coords=string.gsub(coords,'; ',',') end return coords end local function convertCoords (row) local start,finish,lat,lon,coords,says if row then local a,b=string.find(row,"<span class=") start=a while a do -- find the final span> finish=b a,b=string.find(row,"span>",b) end if start then coords= string.sub(row,start,finish) says="" if string.find(coords,'<span class="error">') then error("coord error: badly formed coordinates",0) end coords=coord2text(coords) coords = string.sub(row,1,start-1)..coords..string.sub(row,finish+1) else coords=row end return coords else return "Nothing to see here" end end local function fillCommas(val,max) local line='' if not val then line=',' -- ensure there is some content else line = val --string.lower(string.gsub(val,"%s+","")) -- or strip spaces end if string.find(line,',') == 1 then line=' '..line end -- ensure initial comma is not skipped local _, count=string.gsub(line,",","") -- add enough subsequent commas for all entries line=line..string.rep(',',max-count) while(string.find(line,",,") ) do line=string.gsub(line,",,",", ,") --ensure string.gmatch doesn't ignore any empty items by padding with spaces end return line end local function makeLinkBox(left,top,wid,label, link) local linkBoxName='Transparent square.svg' if visibleLinks or '' =='yes' then linkBoxName='Red hollow square.svg' end local builder = mw.html.create('div') --display:inline-block; local t= string.find(label,'wikidata') -- exclude wikidata from tooltip line if t then label=string.sub(label,1,t-2) -- don't show wikidata value in tooltip if string.find(link,'wikidata') then link = '' end -- don't link on click to wikidata (if there was not another link) end builder :cssText('position:absolute;left:'..tostring(left-1-wid/2)..'px;top:'..tostring(top-1 + math.min(wid/2-12,0) - wid/2)..'px') :wikitext(string.format( '[[File:%s|%dpx|link=%s|%s]]', linkBoxName, wid+2, link, label )) return tostring(builder) end local function extractItem(row,searchItem) -- remove text following a searchItem or start of line, which might be in quote-marks to allow commas local xend,xstart=1,0 if not row then return '','' end if searchItem then xend,xstart= string.find(row or '',searchItem or 'image:') end if not xstart then return string.gsub(string.gsub(row or '',"%b\"\"", ''),"%b\'\'", '') or '','' end while row:byte(xstart+1) == 32 and xstart<#row do -- skip over any leading spaces xstart=xstart+1 end local xbyte=row:byte(xstart+1) if xbyte == 34 or xbyte == 39 then -- are they wrapped in single or double quotes xstart=xstart+1 xend=row:find(string.char(xbyte),xstart+1) else xend = row:find(',',xstart+1) -- if no quotes, we assume no commas if not xend then xend=#row+1 end end -- return residual row and extracted text return row:sub(0,xstart)..row:sub(xend), row:sub(xstart+1,xend-1) end local function itemCheck(item,ext) if not item then return nil end if not ext then ext='' end return (string.match(item,"[%.%-?%d]+") or '0')..ext end local function stripdivs(line) return string.gsub(line or '',"%b<>", ' ') end local function splitItem(item,max) -- takes a commas-sep list and returns a table of lowercase items with no spaces, or nil local r={} local x=1 item=string.lower(fillCommas(item,max)) for t in string.gmatch(item,"[^,]+") do r[x]=string.gsub(t,"%s+","") if r[x]=='' then r[x]= nil else -- residual items might have commas if x>max then r[max]=(r[max] or '')..', '..r[x] end end x=x+1 end return r end local function ParseShapeTypes (result,args,sgval) -- for use with compressed, comma-separated 'sg plus dots' parameters --[[ shape table items and default values as set at top of page shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items label text textSZ="11px", textCL="white", textNG="0deg", textAT=attributes ("bold" and/or "italic") --sgd items for dotTag tagSize="11px", tagColor="darkgrey", tagSpacer="0px", tagAngle="0deg", --sge extension line textEW="1px", textEC="darkblue", textES="solid" --sgf fx for text labels textSP="0px" textLH="100%" textOL="1px", textBG="paleground", <!--| sga = shape,Size,Color,Angle|sgb= outlineWidth,color,style eg: sga1=circle,14px,blue,0deg| sgb1=0px,dark grey,solid | sgc=textSize,color,angle,bold/italic | sgd=tagSize,Color,Spacer,Angle eg: sgc1=11px,dark grey,0deg,normal| sgd1=9px,white,0px,0deg | sge=lineWidth,color,style |sgf=textspacing,lineHeight%,outlinepx,backgroundcolor, [bold,italic] eg: sge1=0px,black, solid| sgf1=0px,120%,0px,background | dot=shape-group/lat/lon/title/dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> --]] if args["sgn"..sgval] then local sgname=string.match(args["sgn"..sgval],"(%w+)(.*)") sgNames[sgname]=sgval end local parent= args["sgp"..sgval] if parent then parent=string.match(parent,"(%w+)(.*)") local pos= string.find(parent,"%d+") if pos == 1 then parent=string.match(parent,"%d+") else parent=sgNames[parent] or '1' end end if sgval~='H' then if not parent or tonumber(parent) > tonumber(sgval) then if sgval=="1" then parent="0" else parent="1" end end end local itemTab, line, filename result[sgval]={} result[sgval].shapeType=sgval line,filename=extractItem(args['sga'..sgval] or '','image:') if sgval=='1' and not args.sga1 then line='circle,12px,blue,0deg' end -- ensure there is a parent=1 sga result[sgval].shapeFile=filename or '' -- sga= Attributes for shape itemTab=splitItem(line,4) result[sgval].shape = itemTab[1] or result[parent].shape result[sgval].shapeSize=itemTab[2] or result[parent].shapeSize result[sgval].shapeColor=itemTab[3] or result[parent].shapeColor result[sgval].shapeAngle=itemCheck(itemTab[4],'deg') or result[parent].shapeAngle -- sgb= Border outline attributes for shape itemTab=splitItem(args['sgb'..sgval],3) result[sgval].outlineWidth=itemCheck(itemTab[1],'px') or result[parent].outlineWidth result[sgval].outlineColor=itemTab[2] or result[parent].outlineColor result[sgval].outlineStyle=itemTab[3] or result[parent].outlineStyle --sgc=character attributes for label itemTab=splitItem(args['sgc'..sgval],4) result[sgval].textSZ=itemCheck(itemTab[1],'px') or result[parent].textSZ -- size of text in px result[sgval].textCL=itemTab[2] or result[parent].textCL -- colour for text result[sgval].textNG=itemCheck(itemTab[3],'deg') or result[parent].textNG -- Angle for text result[sgval].textAT=itemTab[4] or result[parent].textAT -- attributes bold, and/or italic --sgd=dotTag attributes itemTab=splitItem(args['sgd'..sgval],4) result[sgval].tagSize=itemCheck(itemTab[1],'px') or result[parent].tagSize result[sgval].tagColor=itemTab[2] or result[parent].tagColor result[sgval].tagSpacer=itemCheck(itemTab[3],'px') or result[parent].tagSpacer result[sgval].tagAngle=itemCheck(itemTab[4],'deg') or result[parent].tagAngle --sge= extension line attributes itemTab=splitItem(args['sge'..sgval],4) result[sgval].textEW=itemCheck(itemTab[1],'px') or result[parent].textEW -- width result[sgval].textEC=itemTab[2] or result[parent].textEC -- colour result[sgval].textES=itemTab[3] or result[parent].textES -- style --sgf= fx for label text itemTab=splitItem(args['sgf'..sgval],4) result[sgval].textSP=itemCheck(itemTab[1],'px') or result[parent].textSP -- spacing value for letters result[sgval].textLH=itemCheck(itemTab[2],'%') or result[parent].textLH -- Angle for text result[sgval].textOL=itemCheck(itemTab[3],'px') or result[parent].textOL -- width of text-border line result[sgval].textBG=itemTab[4] or result[parent].textBG -- color for text background return result end local function round(x,dec) -- x=number [, dec=integer] returns numeric value with upto dec decimals (all but first trailing zeros get truncated) if (dec or 0)==0 then return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5) --this avoids .0 where dec=0 end dec =10^(dec) return x>=0 and math.floor(x*dec+0.5)/dec or math.ceil(x*dec-0.5)/dec end local function maptogrid(t,r,test) t.lon = tonumber(t.lon) t.lat = tonumber(t.lat) t.lonbase = tonumber(t.lonbase) t.latbase = tonumber(t.latbase) t.zoom = tonumber(t.zoom) or 0 if not t.lat then t.lat = t.latbase or 0 end local x,y x = t.width/2 + (((math.rad(t.lon)*6378137) - (math.rad(t.lonbase)*6378137)) / (156543.03*math.cos(math.rad(t.latbase))/(2^t.zoom))) * (1-(0.075*(math.abs(t.latbase)/90))) y = t.height/2 + ( ( math.log(math.tan(math.rad(t.latbase)/2 + math.pi/4)) * 6378137 - math.log(math.tan(math.rad(t.lat)/2 + math.pi/4)) * 6378137 ) / (156543.03*math.cos(math.rad(t.latbase))/(2^t.zoom)) ) * (1-(0.075*(math.abs(t.latbase)/90))) return round(x,r), round(y,r) end local function getScale(zoom, lat, magVal) zoom = tonumber(zoom) or 0 lat = tonumber(lat) if not lat then lat = 0 end if magVal then magVal = tonumber(magVal) if magVal and magVal > 1 and magVal <= 2 then zoom = zoom + (magVal - 1) end end local dist = (156543.03 * math.cos(math.rad(math.abs(lat))) / (2 ^ zoom)) / 17 local y if dist < 1 then y = round(dist * 10, 1) return tostring(y * 100) .. metres, tostring(round(y * 109, 0)) .. yards elseif dist < 18 then y = round(dist, 0) return tostring(y) .. km, tostring(round(y * 0.621371, 1)) .. miles elseif dist < 500 then y = round(dist / 10) return tostring(y * 10) .. km, tostring(round(y * 6.21371, 0)) .. miles else y = round(dist / 100) return tostring(y * 100) .. km, tostring(round(y * 62.1371, 0)) .. miles end end local function ParseData (args,dotval) -- for use with compressed, comma-separated 'sg plus dots' parameters -- takes a structured series of comma-separated items which get parsed as the following: -- dot(n)= (sgNumber or Name),{{coord}} or (lat,lon), (dotTag) -- dotlink(n) = single-parameter text to give wikilink and/or title used by tootlip, fullscreen dots and autocaption list -- dotlabel(n) = 'label text',pos(left,roght,top,bottom,centre,auto),(dx), (dy) pixel offsets, params, info -- dotpic(n) = single parameter wikimedia filename for an image to use in photopanel and/or fullscreen dots -- dotfeature(n)= 'mark-line' (,linewidth,style,gap) or 'photo-panel' (,image-dim,panel-width,panel-height), draws line to n-1 -- label is used if either a position and/or an x,y offset are not 0,0 ( if no label then dotTag will be put at at the x,y offset, or over the dot -- label text can be autoaligned if x,y puts it left or right of the dot, or centered if above/below) -- quote marks are not needed unless including commas within the label text -- param1 is optional items separated by spaces, and can include [nolabel nolist nomap hemisphere+1 hemisphere-1] -- info is free wikitext, to be used in the fullscreen dot box. (use dotpic to show a picture) --<!--| dotx=shape-group,[lat,lon or {{coord}} ], dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> local result={} local count=1 local row = convertCoords (args["dot"..dotval]) -- swap in any {{coord}} values so they are csv lat and lon row=fillCommas(row,4) result.code=dotval -- store the parameter name as the id code for item in string.gmatch(row,"[^,]+") do -- iterate through 'row', adding each csv item in turn, if present if count==1 then --see if it is a number or a name local pos= string.find(item,"%d+") if pos == 1 then result.group=string.lower(string.gsub(item,"%s+","")) else item=string.match(item,"(%w+)(.*)") -- ensure just a single word result.group=sgNames[item] end elseif count==2 then result.lat=tonumber(string.match(item,"[%.%-?%d]+")) or 0-- find the number, with no non-numeric stuff elseif count==3 then result.lon=tonumber(string.match(item,"[%.%-?%d]+")) or 0 elseif count==4 then result.dotTag=item:match( "^%s*(.-)%s*$" ) or "" -- dotTag allows for internal spaces, but no commas end count=count+1 end row, result.labelText= extractItem(args["dotlabel"..dotval]) result.labelText= string.gsub(result.labelText,"[%^]+","<br>") -- convert hats to line breaks local item=splitItem(row,6) result.labelPos=item[2] or 'center' result.dx=tonumber(string.match(item[3] or '0',"[%.%-?%d]+")) or 0 result.dy=tonumber(string.match(item[4] or '0',"[%.%-?%d]+")) or 0 result.param1=string.lower(item[5] or '') if string.find(result.param1,"hemisphere-1",1,true) then result.lon=result.lon-360 elseif string.find(result.param1,"hemisphere+1",1,true) then result.lon=result.lon+360 end local txt = '' if item[6] then -- ensure all info elements are included, including commas count=1 local max=6 for t1 in string.gmatch(fillCommas(row,max),"[^,]+") do if count>max then txt=txt..',' end if count>=max then txt=txt..t1 end count=count+1 end end result.info=txt result.imageName = args['dotpic'..dotval] -- Get first wikilinked item (if any) from the args.dotlink and set this plus the delinked text local testx=args["dotlink"..dotval] or '' result.dotLink=testx if testx ~= '' then testx=stripdivs(testx) result.title=delink({ testx }) local linkstart= string.find(testx,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then result.dlink=delink( { string.sub(testx,linkstart,string.find(testx,']]',1,true)+1),wikilinks='target' } ) else result.dlink='' end else result.dlink='' result.title='' end if args['dotfeature'..dotval] then local item=splitItem(args['dotfeature'..dotval],6) if (item[1] or '') =='photo-panel' then result.ppwidth= tonumber(string.match((item[3] or '110'),"%d+")) -- panel width result.ppheight= tonumber(string.match((item[4] or '48'),"%d+") ) --panel height result.photowidth=round(tonumber(string.match((item[2] or '1.3'),"[%.%-?%d]+")) * result.ppheight+1,0) -- dimension to set image size result.photoImage=result.imageName result.posType='photo-panel' elseif (item[1] or '') == 'mark-line' then local x=tonumber(dotval or '0') result.markDest=item[5] or tostring(x-1) result.mlWidth= tonumber(string.match((item[2]) or '',"%d+") or '1') result.mlStyle= item[3] or 'solid' result.mlGap=tonumber(string.match((item[4] or ''),"[%d]+") or '0') result.posType='mark-line' end --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end maplist.lon=result.lon maplist.lat=result.lat result.gridx, result.gridy = maptogrid(maplist,1) -- convert geo coords to grid xy - using values from maplist table return result end local function multiCheck (args, argName, argVal, defVal, alt) if not alt then alt='nonexistant' end if argVal=='H' and not args[argName..'H'] then argVal=highlightNum or '1' end if argVal=='' then return (args[argName] or args[alt] or (args[argName..'D']) or args[alt..'D'] or defVal) -- unnumbered args do not inherit from D or 1 else return (args[argName..argVal]) or (args[alt..argVal]) or (args[argName..'D']) or (args[alt..'D']) or (args[argName..'1']) or (args[alt..'1']) or defVal end end local function htmlTableLine (htmlTable,h1,h2,h3,h4,h5,type) if type == 'header' then if (h4 or '') ~= 'no-year' then h4='<th class="unsortable">'..h4 or ''..'</th>' else h4 = '' end if (h5 or '') ~= '' then h5='<th class="unsortable">'..h5..'</th>' else h5 = '' end return htmlTable..'<tr><th>'..(h1 or '')..'</th><th>'..(h2 or '')..'</th><th>'..(h3 or '')..'</th>'..h4..h5..'</tr>' else if (h4 or '') ~= 'no-year' then h4='<td>'..h4 or ''..'</td>' else h4 = '' end if (h5 or '') ~= '' then h5='<td>'..h5..'</td>' else h5 = '' end return htmlTable..'<tr><td>'..(h1 or '')..'</td><td>'..(h2 or '')..'</td><td>'..(h3 or '')..'</td>'..h4..h5..'</tr>' end end local function formatInt(number) if number == 0 then return notFound end local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)') int = int:reverse():gsub("(%d%d%d)", "%1,") return minus .. int:reverse():gsub("^,", "") .. fraction end local function convertToSqKm (value,punit) if string.find(punit, 'Q35852') then value = value / 100 -- convert to sq km if it's hectares elseif string.find(punit, 'Q232291') then value = value /(1/2.59) -- or this if it's square miles elseif string.find(punit, 'Q81292') then value = value / 247.1 -- or this if it's acres end return value end local function assignTradstyleShape(shapeResult,default,dotResult,args,nval) local item,itemTab local autoDotTag='' local shapeWidth,shapeHeight=0,0 local argval=nval -- to catch the unnumbered shape series local qvalue='' if argval=='0' then argval='' end if nval=='H' then shapeResult.H={} end -- Assign autovalues if a wikidata Qvalue has been supplied -- some preliminary documentation -- geo-data-type= default is P1082 (population) -- other options might include number of households (P1538) and per capita income (P10622) or land area (P2046) -- geo-data-list= this is a comma-separated list of Q values, for a manually generated list that doesn't require nymbered parameters -- geo-scalefactor = can enlarge or shrink all the dots in proportion, depending an what the map needs -- geo-region = needs to be a Qvalue of the larger administrative region that the settlements/sub-regions are in -- geo-sub-region needs to be a Pvalue, but will default to P150 (some pages use 'has parts' = P527) -- geo-data3= etc numbered Qvalue item, if used in conjunction with other shape/label parameters -- shapeD= sets default shape, which migh be n-circle to include numbers. -- geo-number-size= will give a fixed size for shape numbers. (defaults to 12 when using proportional dots) -- geo-boundaries= if set to 1 will add any Qvalues for the sub-regions to 'map-data-light'. -- show-geo-dots= to use proportional dots. Will be fixed size if 0. (set mark-sizeD=0 to have no dot at all, eg to show labels or boundaries) -- find-geo-dots=1 or 0 (needs to also include geo-region) -- label-posD = gets additional options: [off] gives no label, [on] will position lower centre, -- [on+] includes numeric values beside/below label, [on^] will always put it below. [on-] always puts values beside if args['geo-data'..argval] then -- and (args['mark-title'..argval] or '') ~= 'none' then local pvalue = args['geo-data-type'] or 'P1082' local plabel = mw.wikibase.getLabel(pvalue) if not args['geo-number-size'] then args['geo-number-size']=12 end if not args['label-posD'] then args['label-posD'] = 'on' end qvalue=args['geo-data'..argval] -- total poulation is a Qvalue wikidata page entry, or add up all the geo-data values or use a random 10000 local region = args['geo-region'] -- or mw.wikibase.getEntityIdForCurrentPage() local thisPop=0 local areatxt='' local yearVal='' if mw.wikibase.getBestStatements(qvalue,pvalue)[1] then thisPop=tonumber(mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.amount) or 0 if pvalue == 'P2046' then -- area, so convert from ha to sq km if needed (/100) thisPop = convertToSqKm(thisPop, mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.unit) end if mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers and mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers.P585 then yearVal = mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers.P585[1].datavalue.value.time if yearVal then yearVal = yearVal:sub(2,5) end -- wikidata reports an entire date and time, so extract the year end else debugmsg(mw.addWarning(mw.wikibase.getLabel(qvalue)..' ('..qvalue..') has no recorded '..plabel)) end local scalefactor=args['geo-scalefactor'] or 1 if argval=='1' and args['mark-size1'] and not args['mark-sizeD'] then args['mark-sizeD'] = args['mark-size1'] end if argval=='1' and args['label-pos1'] and not args['label-posD'] then args['label-posD'] = args['label-pos1'] end if not args['mark-size'..argval] then if args['show-geo-dots'] == '0' then args['mark-size'..argval] = args['mark-sizeD'] or '1' -- just use a 'point' for the label to attach to else if thisPop>0 then args['mark-size'..argval] = round(math.sqrt(thisPop/geoTotalPop*100) * 8 * scalefactor,0) else args['mark-size'..argval] = args['mark-sizeD'] or '1' args['shape-outline'..argval]=args['shape-outline'..argval] or args['shape-outlineH'] end end end local posTemp=string.lower(multiCheck(args,'label-pos',argval,'on')) args['label-pos'..argval] = posTemp posTemp=splitItem(posTemp,2)[1] if pvalue == 'P2046' then thisPop = round(thisPop,2) areatxt=sqkm..areatxt -- round decimals if sq km area elseif pvalue == 'P10622' then if mw.wikibase.getBestStatements(qvalue,pvalue)[1] then local punit=(mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.unit or ''):match("Q.*") -- extract Q value if punit == 'Q4917' then areatxt=' US$' elseif punit == 'Q4916' then areatxt=' euro' elseif punit == 'Q25224' then areatxt=' £GBP' else areatxt=mw.wikibase.getLabel(punit)..areatxt end end end if posTemp=='off' then else if not args['label'..argval] then args['label'..argval] = mw.wikibase.getLabel(qvalue) end local tline=posTemp:find("[%+%^%-]") if tline then local tval=posTemp:sub(tline,tline) local eline posTemp=posTemp:sub(1,tline-1) if posTemp == 'left' or posTemp == 'right' or string.find(posTemp,'east') or string.find(posTemp,'west') then if tval == '^' then eline = '^' else eline = ' ' end -- only add line break if 'forced', used for left/right elseif tval == '+' or tval == '^' then eline = '^' -- add linebreak unless 'forced' not to else eline = ' ' -- must be a dash, so no line break end args['label'..argval] = args['label'..argval]..eline..'('..formatInt(thisPop)..')' args['label-pos'..argval] = posTemp..args['label-pos'..argval]:sub(tline+1,#args['label-pos'..argval]) end end if posTemp=='on' then if argval=='1' and (not args.ldyD) then args.ldyD = args['ldy'..argval] or '0' end if not args['ldy'..argval] then args['ldy'..argval] = args.ldyD end -- ensure ldy for that dot is added to the position args['label-pos'..argval]='center' local _,br = string.gsub(args['label'..argval],'%^','') local item=splitItem(args['label-size'..argval],2) args['ldy'..argval] = (args['ldy'..argval] or 0) + ((item[1] or 11) * ((br+1)*0.6)) + ((args['geo-number-size'] or 12) / 2) end local nt='' if args['mark-title'..argval] == 'nolist' then nt='nolist,' end if not args['mark-title'..argval] or args['mark-title'..argval] == '' or args['mark-title'..argval] == 'nolist' then if mw.wikibase.getSitelink(qvalue) then args['mark-title'..argval] = nt..'[['..mw.wikibase.getSitelink(qvalue)..']]' else args['mark-title'..argval] = nt..mw.wikibase.getLabel(qvalue) end end -- If mark-title is not set locally then attempt Sitelink value, or use label if thisPop == 0 then areatxt='' end captionTableText[argval]={} captionTableText[argval][1]= args['mark-title'..argval] captionTableText[argval][2]= formatInt(thisPop) if maplist.width >= 280 then captionTableText[argval][3]= yearVal else captionTableText[argval][3]='no-year' end if args['show-q-values'] == '1' then captionTableText[argval][4]='<small>[[wikidata:'..qvalue..'|'..qvalue..']]</small>' else local nbsp = '&nbsp;' if maplist.width < 230 then nbsp = ' ' end captionTableText[argval][4]='' if captionTableText[argval][3] == 'no-year' then captionTableText[argval][2] = captionTableText[argval][2]..'<small><sup>'..nbsp..'[[wikidata:'..qvalue..'|WD]]</sup></small>' else captionTableText[argval][3] = captionTableText[argval][3]..'<small><sup>'..nbsp..'[[wikidata:'..qvalue..'|WD]]</sup></small>' end end if args['mark-title'..argval] == 'none' then -- no title else args['mark-title'..argval] = args['mark-title'..argval]..' ('..plabel..' '..formatInt(thisPop)..areatxt..')' end if not args['mark-description'..argval] then args['mark-description'..argval] = (mw.wikibase.getDescription(qvalue) or '')..'<br>' end if (not args['mark-image'..argval]) and (mw.wikibase.getBestStatements(qvalue,'P18')[1]) then args['mark-image'..argval] = mw.wikibase.getBestStatements(qvalue,'P18')[1].mainsnak.datavalue.value end item = multiCheck(args,'shape',argval,'circle') --default to circle if geo-data if not string.find(item,'svgpath,') then item=string.lower(item) end -- ensure svgpath items retain original case values else item = multiCheck(args,'shape',argval,'image') if not string.find(item,'svgpath,') then item=string.lower(item) end -- ensure svgpath items retain original case values end if string.find(item,'n-',0,true)==1 or string.find(item,'l-',0,true)==1 then autoDotTag=string.sub(item,0,1) item=string.sub(item,3) end if item == 'image' then shapeResult[nval].shape = 'image:' shapeResult[nval].shapeFile =multiCheck(args,'mark',argval,'Red pog.svg') shapeWidth=-1 else shapeResult[nval].shape = item end item= multiCheck(args,'mark-size',argval,(shapeResult[nval].shapeSize or '14px')) -- add geo-data as fallback if already assigned local a,b,c= getsize(string.gsub(string.gsub(item,',','px')..'px','pxpx','px')) if b==a and args['mark-dim'..argval] then b= b / tonumber(string.match(args['mark-dim'..argval],"[%.%-?%d]+")) end shapeHeight=b/2 item=tostring(a)..'px'..tostring(b)..'px'..tostring(c)..'px' shapeResult[nval].shapeSize= item itemTab=splitItem(multiCheck(args,'shape-color',argval,'hard red'),2) shapeResult[nval].shapeColor=itemTab[1] or 'hardred' item=itemCheck(itemTab[2],'%') -- jump through the various opacity hoops and add to color if needed if not item then item=itemCheck(args['shape-opacity'..argval],'%') end if item and item~='0%' and item~='100%' then shapeResult[nval].shapeColor=shapeResult[nval].shapeColor..item end shapeResult[nval].shapeAngle=itemCheck(multiCheck(args,'shape-angle',argval,'0'),'deg') or '0deg' if args['shape-angle'..argval] then shapeResult[nval].shapeAngleArg=true else shapeResult[nval].shapeAngleArg=false end --sort out the outline entry itemTab=splitItem(multiCheck(args,'shape-outline',argval,'transparent,0,100,solid'),4) shapeResult[nval].outlineColor=itemTab[1] or 'dark grey' shapeResult[nval].outlineWidth=itemCheck(itemTab[2],'px') or '1px' if itemTab[3] and itemTab[3]~='100' and itemTab[3]~='0' then shapeResult[nval].outlineColor=shapeResult[nval].outlineColor..itemCheck(itemTab[3],'%') end shapeResult[nval].outlineStyle=itemTab[4] or 'solid' -- label size, background, outline shapeResult[nval].textAT=args['label-attribute'..argval] or '' itemTab=splitItem( multiCheck(args,'label-size',argval,'12'),3) shapeResult[nval].textSZ=itemCheck(itemTab[1],'px') or '12px' if itemTab[2]=='outline' then shapeResult[nval].textBG=itemTab[3] or 'transparent' shapeResult[nval].textOL='1px' elseif itemTab[3]=='outline' then shapeResult[nval].textBG=itemTab[2] or 'transparent' shapeResult[nval].textOL='1px' else shapeResult[nval].textOL='0px' shapeResult[nval].textBG=itemTab[2] or 'transparent' end if getColor(shapeResult[nval].textBG)==CTB['hardgrey'] and shapeResult[nval].textBG~='hardgrey' then shapeResult[nval].textBG= 'transparent' end --label color etc itemTab=splitItem(multiCheck(args,'label-color',argval, 'darkgrey','label-colour'),2) shapeResult[nval].textCL=itemTab[1] or 'darkgrey' if itemTab[2] and itemTab[2]~='0%' and itemTab[2]~='100%' then shapeResult[nval].textCL=shapeResult[nval].textCL..itemTab[2] end shapeResult[nval].textSP=itemCheck( multiCheck(args,'label-spacing',argval,'0'),'px') -- sets letter-spacing in px shapeResult[nval].textLH=itemCheck( multiCheck(args,'label-height',argval,'120'),'%') -- sets line height, 120% default shapeResult[nval].textNG=itemCheck(multiCheck(args,'label-angle',argval,'0'),'deg') if args['label-angle'..argval] then shapeResult[nval].textNGarg=true else shapeResult[nval].textNGarg=false end --sgd=dotTag attributes shapeResult[nval].tagSize=(args['geo-number-size'] or tostring(shapeHeight*1.5))..'px' local c1,c2=checkColors(shapeResult[nval].shapeColor) shapeResult[nval].tagColor=c2 shapeResult[nval].tagSpacer='0px' shapeResult[nval].tagAngle='0deg' -- sge extension line attributes local shapePos=splitItem(multiCheck(args,'label-pos',argval,'right'),6) if shapePos[2]=='with-line' or shapePos[2]=='n-line' then shapeResult[nval].textEW=(shapePos[3] or '1')..'px' -- width shapeResult[nval].textEC=(shapePos[4] or shapeResult[nval].shapeColor or 'darkgrey') elseif shapePos[2]=='photo-panel' then shapeResult[nval].textEW='2px' -- width shapeResult[nval].textEC=shapeResult[nval].textCL else shapeResult[nval].textEW='0px' -- width shapeResult[nval].textEC='grey'-- colour end shapeResult[nval].textES='solid' if argval=='H' then return dotResult end --Assign dot values local dotItem={} dotItem.group=nval dotItem.code=nval dotItem.posType=shapePos[2] or 'nil' if (shapePos[2] or '') =='photo-panel' then dotItem.ppwidth= tonumber(string.match((shapePos[4] or '110'),"%d+")) dotItem.ppheight= tonumber(string.match((shapePos[5] or '48'),"%d+") ) dotItem.photowidth=round(tonumber(string.match((shapePos[3] or '1.3'),"[%.%-?%d]+")) * dotItem.ppheight+1,0) dotItem.photoImage=args['mark-image'..argval] --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end if (shapePos[2] or '') =='mark-line' then local x=tonumber(argval or '1') dotItem.markDest=shapePos[6] or tostring(x-1) dotItem.mlWidth= tonumber(string.match((shapePos[3] or '1'),"%d+")) dotItem.mlStyle= shapePos[4] or 'solid' dotItem.mlGap=tonumber(string.match((shapePos[5] or '0'),"[%d]+")) shapeResult[nval].textEC=shapeResult[nval].outlineColor or 'darkgrey' end if args['mark-coord'..argval] then itemTab=splitItem(convertCoordsTrad (args['mark-coord'..argval]),2) if itemTab[1] == nil or itemTab[2] == nil then debugmsg(mw.addWarning('Unable to read coordinates for mark-coord'..argval)) else dotItem.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) or 0 end else dotItem.lat=tonumber(string.match(args['mark-lat'..argval],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(args['mark-lon'..argval],"[%.%-?%d]+")) or 0 end if args['dateline'..argval] and (args['dateline'..argval]=='1' or args['dateline'..argval]=='-1') then dotItem.lon=dotItem.lon+(tonumber(args['dateline'..argval] ) *360) end maplist.lon=dotItem.lon maplist.lat=dotItem.lat dotItem.gridx, dotItem.gridy = maptogrid(maplist,1) local item=args['mark-title'..argval] or '' -- sort out the caption, wikilink and plaintext tooltip items from dotLink if item=='none' then dotItem.param1='nomap nolist' item='' end if string.find(item,'nolist') then dotItem.param1='nolist,' item = string.sub(args['mark-title'..argval],8) or args['label'..argval] end dotItem.dotLink=item if item ~= '' then item=stripdivs(item) dotItem.title=delink({item}) local linkstart= string.find(item,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then dotItem.dlink=delink({string.sub(item,linkstart,string.find(item,']]',1,true)+1),wikilinks='target'}) -- debugmsg('dlink for '..item..' is '..dotItem.dlink) else dotItem.dlink='' end else dotItem.dlink='' dotItem.title='' end if autoDotTag=='n' then item=nval elseif autoDotTag=='l' then item=string.char(64+tonumber(nval)) else item='' end dotItem.dotTag = args['numbered'..argval] or item if shapePos[2]=='n-line' and (args['label'..argval] or args['label'..argval]=='') then if dotItem.dlink =='' then item=dotItem.dotTag..' '..args['label'..argval] else item='[['..dotItem.dlink..'|'..dotItem.dotTag..']] '..args['label'..argval] end else item=(args['label'..argval] or '') end if args['labela'..argval] then item = item..'^'..args['labela'..argval] end if args['labelb'..argval] then item = item..'^'..args['labelb'..argval] end local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end dotItem.labelText = a -- convert hats to line breaks if argval=='' then item = (args['label-offset-x']) or (args.ldx) or '0' else item=args['label-offset-x'..argval] or args['ldx'..argval] or args['label-offset-xD'] or args.ldxD or args['label-offset-x1'] or args.ldx1 or '0' end dotItem.dx=tonumber(string.match(item,"[%.%-?%d]+")) if argval=='' then item = (args['label-offset-y']) or (args.ldy) or '0' else item=args['label-offset-y'..argval] or args['ldy'..argval] or args['label-offset-yD'] or args.ldyD or args['label-offset-y1'] or args.ldy1 or '0' end dotItem.dy=tonumber(string.match(item,"[%.%-?%d]+")) dotItem.labelPos=shapePos[1] if args['mark-image'..argval] then dotItem.imageName= args['mark-image'..argval] end dotItem.info=args['mark-description'..argval] or '' table.insert(dotResult,1,dotItem) -- add to start of list, so they are in reverese order for displaying -- debugmsg(dotItem.code..' = '..dotItem.lat..','..dotItem.lon..': '..dotItem.labelText..' - '..dotItem.title..', '..shapeResult[nval].shapeSize) return dotResult end local function tradstyleParseShapes(args,dotTable,dotmax) local sgNumbers,sgSortable={},{} for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['geo-data'..x] and (not args['mark-coord'..x]) and (not args['mark-lat'..x]) then -- get lat and lon values from wikidata and put them into the args.lat and args.lon for use wherever needed if (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1]) then args['mark-lat'..x] = (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1].mainsnak.datavalue.value.latitude) or '0' args['mark-lon'..x] = (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1].mainsnak.datavalue.value.longitude) or '0' local p=args['geo-data-type'] or 'P1082' if mw.wikibase.getBestStatements(args['geo-data'..x],p)[1] then local n = tonumber(mw.wikibase.getBestStatements(args['geo-data'..x],p)[1].mainsnak.datavalue.value.amount) or 0 if p == 'P2046' then -- area, so convert to sq km if needed (/100) n = convertToSqKm( n, mw.wikibase.getBestStatements(args['geo-data'..x],p)[1].mainsnak.datavalue.value.unit) end geoTotalPop = geoTotalPop + n end else debugmsg(mw.addWarning('could not find coords for '..mw.wikibase.getLabel(args['geo-data'..x])..', '..args['geo-data'..x])) end end if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) then sgNumbers[x]=x -- add it to the list end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form (needs a second table, seemingly) if args['mark-coord'] or (args['mark-lat'] and args['mark-lon']) then table.insert(sgSortable,'0') end -- add it to the end of the list local default={} default.D={} local dotResult={} for k,sgnum in pairs(sgSortable) do -- work through the sorted list, parsing each set of shapes in turn, from 1 upwards shapeList[sgnum]={} dotTable=assignTradstyleShape(shapeList,default,dotTable,args,sgnum) end dotTable=assignTradstyleShape(shapeList,default,dotTable,args,'H') -- construct an extra highlight shapeitem local unitConvert=1 local areaUnits='' if args['geo-region'] and (args['find-geo-dots'] or '1') ~= '0' then local region = args['geo-region'] -- or mw.wikibase.getEntityIdForCurrentPage() local pvalue = args['geo-data-type'] or 'P1082' local plabel = mw.wikibase.getLabel(pvalue) local subhead='' -- Assemble the subhead content, a line of text about the region as a whole if region and mw.wikibase.getBestStatements(region,pvalue)[1] then local punit=mw.wikibase.getBestStatements(region,pvalue)[1].mainsnak.datavalue.value.unit:match("Q.*") -- extract Q value if pvalue == 'P10622' then -- add other currency-based pvalues here areaUnits=mw.wikibase.getLabel(punit) end if punit == 'Q35852' then unitConvert=100 areaUnits = ' '..sqkm -- if its hectares, convert to sq km elseif punit == 'Q232291' then unitConvert = (1/2.59) areaUnits = ' '..sqkm -- or this if it's square miles elseif punit == 'Q81292' then unitConvert = 247.1 areaUnits = ' '..sqkm -- or this if it's acres elseif punit == 'Q712226' then unitConvert = 1 areaUnits = ' '..sqkm -- or this if it's already sq km elseif punit == 'Q4917' then areaUnits=' US$' -- replace long version with some shortform currencies () elseif punit == 'Q4916' then areaUnits=' euro' elseif punit == 'Q25224' then areaUnits=' £GBP' end -- if pvalue == 'P2046' then -- may need to add other instances of sq km so add in other P-items here if they need units showing -- end geoTotalPop=(mw.wikibase.getBestStatements(region,pvalue)[1].mainsnak.datavalue.value.amount) / unitConvert local yearVal if mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers then yearVal = mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P585[1].datavalue.value.time if yearVal and yearVal:sub(1,1) == '+' then yearVal = yearVal:sub(2,5) if mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459 and mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459[1] then local methodVal = mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459[1].datavalue.value.id if methodVal then yearVal=yearVal..' '..mw.wikibase.getLabel(methodVal) end end yearVal = ' ('..yearVal..')' else yearVal ='' end end subhead=totalItems..' '..mw.wikibase.getLabel(region)..' '..plabel..' ([[wikidata:'..region..']]) = '..formatInt(round(geoTotalPop),1)..areaUnits..yearVal else if pvalue == 'P2046' then areaUnits = sqkm end subhead=totalItems..' '..mw.wikibase.getLabel(region)..' '..plabel..' ('..sumOfDatapoints..') = '..formatInt(round(geoTotalPop),1)..areaUnits end if maplist.width < 350 and #plabel > 4 then plabel=string.sub((plabel or ' '),1,3)..'.' end if maplist.width < 330 then numberCol = ' ' end if maplist.width < 280 then year = 'no-year' end captionTable = subhead if (args['show-q-values'] or '') ~= '1' then captionTable = captionTable..'<div style="float:right;"><small> (WD&nbsp;=&nbsp;'..wikidataLink..')</small></div><br>' wikidataLink='' end captionTable = captionTable..'<table class="wikitable plainrowheaders sortable mw-collapsible mw-collapsed" style="width: 100%;"><caption>'..(args.toggletext or toggletext)..' </caption>' captionTable = htmlTableLine(captionTable,numberCol,mw.wikibase.getLabel(region),plabel..' '..areaUnits, year, wikidataLink,'header') end return dotTable end local function checkfortooltip (title,dx,dy,dotlabel,dlink,nolabel) -- returns tlink if available, and dlink, if needed and tshape=true if shape needed local tshape,tlink = false,"" if dlink~='' and not nolabel then tlink=dlink end if (tlink=="" or nolabel) and title~="" then tshape=true end -- tshape flags True if title is wanted for shape if (not (dx==0 or dy==0) or dotlabel=='') and title~='' then tshape=true end -- add tooltip to shape if its number has moved return tshape,tlink end local function tshift(angle) -- adjustment to place text near the centre of a triangle, shifted to allow rotation of triangle shape local x=tonumber(string.match(angle,"%-?%d+")) if x<0 then x=360+x end -- set to a single degree direction, 0 to 360 if x>359 then return 0,0 end -- shift the centre of the triangle based on rotation value if x <=40 or x>=320 then return -0.17,0 -- triangle up= -shiftv elseif x>=140 and x<=220 then return 0.17,0 --triangle down= +shiftv elseif x >220 then return 0,-0.17 --triangle left= -shifth elseif x >40 then return 0,0.22 --triangle right= +shifth end return 0,0 end local function makeTriangle(result,row,shape,outline,tlink) local w,h,r=getsize(shape.shapeSize) if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) w=w+p*2 h=h+p*2 end table.insert(result,'<div ') if tlink then table.insert(result,' title="'..row.title..'" ') end table.insert(result,'style="display:inline-block; position: absolute') if shape.shapeAngle ~= '0deg' then table.insert(result,'; transform: rotate('..shape.shapeAngle..')') end local shiftv,shifth=0,0 shiftv,shifth=tshift(shape.shapeAngle) table.insert(result,'; top: '..tostring(row.gridy-h/2+h*shiftv)..'px') table.insert(result,'; left: '..tostring(row.gridx-w/2+w*shifth)..'px; width: 0; height: 0; outline-width: 0px') table.insert(result,'; border-left: '..tostring(w/2)..'px solid transparent') table.insert(result,'; border-right: '..tostring(w/2)..'px solid transparent') if outline then -- fill with outline colour, to make a 'base layer' or shape colour table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.outlineColor).. '">') else table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.shapeColor).. '">') end table.insert(result,'</div>') end local function makeSquare(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) if shape.shape=='square' then h=w end -- squares are always square!. box can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end if r~=0 then div:css('border-radius',tostring(r).."px") end if shape.shape=='panel' then div:css('top', tostring(row.gridy).."px") div:css('left', tostring(row.gridx).."px") else div:css('top', tostring(row.gridy-h/2).."px") div:css('left', tostring(row.gridx-w/2).."px") end div:css('width', tostring(w).."px") div:css('height', tostring(h).."px") div:css('background-color', getColor(shape.shapeColor) ) div:css('color', 'inherit') table.insert(result,tostring(div)) end local function makeCircle(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding if shape.shape=='circle' then h=w end -- circles are always round. ellipse can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end div :css('top', tostring(row.gridy-h/2).."px") :css('left', tostring(row.gridx-w/2).."px") :css('width', tostring(w).."px") :css('height', tostring(h).."px") :css('border-radius', "50%") :css('background-color', getColor(shape.shapeColor) ) :css('color', 'inherit') table.insert(result,tostring(div)) end local function makeRuleA(result,row,shape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local lineV=0 if shape.shape=='rulea' then lineV=oWid*3+16 end table.insert(result,"<div style=\"display:inline-block; position: absolute") -- create a square transparent container, which will rotate line and arrow together table.insert(result,"; top:"..tostring(row.gridy - w/2).."px") table.insert(result,"; left:"..tostring(row.gridx - w/2).."px") table.insert(result,"; width:"..tostring(w).."px") table.insert(result,"; height:"..tostring(w).."px; background:transparent; color:inherit;") table.insert(result,"; transform: rotate( "..tostring(tonumber(string.match(shape.shapeAngle,"[%.%-?%d]+")) - 90).."deg);\">" ) table.insert(result,"<div style=\"display:inline-block; position: absolute") -- put the line (as a border-right) across the container table.insert(result,"; top:"..tostring(lineV).."px") table.insert(result,"; left:"..tostring((w - oWid )/2).."px; width: 0px") table.insert(result,"; height: "..tostring(w -lineV).."px") table.insert(result,"; border-right: "..shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) table.insert(result,"; background:transparent; color:inherit;\"></div>") if shape.shape=='rulea' then table.insert(result,"<div style=\"display:inline-block; position: absolute; top: 0px") --and add arrow head table.insert(result,"; left:"..tostring(w/2-( oWid/2)-oWid*0.55-2).."px; width: 0; height: 0; outline-width: 0px") table.insert(result,"; border-left: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-right: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-bottom: "..tostring(oWid*3+16).."px solid "..getColor(shape.outlineColor).."\"></div>") end table.insert(result,"</div>") end local function makeCurveA(result,row,shape) -- draw a curve with Arrow ----- local w,h=getsize(shape.shapeSize) -- = width,height local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local Angle=tonumber(string.match(shape.shapeAngle,"[%.%d]+")) table.insert(result,'<div style="position: absolute;') --set up out div, which will allow the whole to rotate table.insert(result,'top:'..tostring(row.gridy - (w + oWid*3+16)/2)..'px;') table.insert(result,'left:'..tostring(row.gridx - ( w + oWid*3+16)/2)..'px; ') table.insert(result,'width: '..tostring(w+oWid*3+16)..'px; ') table.insert(result,'height: '..tostring(w+oWid*3+16)..'px; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate( '..tostring(Angle-120)..'deg);">') else table.insert(result,'transform: rotate( '..tostring(Angle -62)..'deg);">') end table.insert(result,'<div style="position: absolute;') --set up div for the rounded corner of a rectangle table.insert(result,'border-left: '..shape.outlineWidth..' '..shape.outlineStyle..' '..getColor(shape.outlineColor)..';') if shape.shape=='curvea' then table.insert(result,'border-radius: 10000px 0 0 '..tostring(w)..'px; top:0px; left:'..tostring(w*0.25)..'px;') else table.insert(result,'border-radius: '..tostring(w)..'px 0 0 10000px;') table.insert(result,'top:'..tostring((oWid*3+16)/2+w*0.15)..'px; left:'..tostring(w*0.25)..'px;') end -- and add a triangular arrow head table.insert(result,'width: '..tostring(w)..'px; height: '..tostring(w)..'px;"></div><div style="position: absolute; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate(180deg); top:'..tostring(w-1)..'px; ') else table.insert(result,'transform: rotate(0deg); top: '..tostring(0-( ( oWid*3+16)/2)+1+( w*0.15) )..'px;') end -- reverse table.insert(result,'left:'..tostring(0-( oWid*0.6)-2+(w*0.25))..'px;') table.insert(result,'width: 0; height: 0; outline-width: 0px; border-left: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-right: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-bottom: '..tostring(oWid*3+16)..'px solid '..getColor(shape.outlineColor)..';"></div></div>') end local function makeLineTo (result,x1,y1,x2,y2,oWid, oStyle, oCol,double,shape) table.insert(result,"<div style=\"display:inline-block; position: absolute;") -- draw a line between x1,y1 and x2,y2, px-coords where 0,0 is centre of frame -- Maths calculations thanks to ES local tw if double <= 1 then tw = tonumber(string.match(oWid,"[%.%d]+"))/2 -- shift centre by half the line width else tw = tonumber(string.match(oWid,"[%.%d]+")) + (double/2) - 0.5 -- shift centre by a line width + half the gap end -- debugmsg(oWid..'+'..double..'/2='..tw) table.insert(result,"left: "..tostring(x1+( (x2-x1)/2) - (math.sqrt( ( x2-x1)^2 + (y2-y1)^2 )/2) ).."px;") table.insert(result,"top: "..tostring(y1+( ( y2-y1 )/2) - tw).."px;") table.insert(result,"width: "..tostring(math.sqrt( (x2-x1 )^2 + ( y2-y1 )^2) ).."px;") table.insert(result,"height: "..tostring(double).."px; background-color:transparent; color:inherit; ") table.insert(result,"outline-width: 0; border-bottom: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) if double>1 then table.insert(result,"border-top: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) end local lineAngle=math.atan(( y2-y1)/( x2-x1 ) )*180/math.pi -- if x1 == x2 then lineAngle=90 end table.insert(result,"transform: rotate("..lineAngle.."deg);\"></div>") -- debugmsg('text angle started as '..shape.textNG..' and lineAngle = '..lineAngle) if (lineAngle < -90) or (lineAngle >90) then lineAngle=lineAngle-180 end if shape and not shape.textNGarg then shape.textNG=tostring(round(lineAngle,0))..'deg' end -- Put in line angle for a default text angle as well -- if (y2 >= y1) and (x2 <= x1) then lineAngle=lineAngle-180 debugmsg('arrow angle corrected') end if x2 < x1 then lineAngle=lineAngle-180 end if shape and not shape.shapeAngleArg and shape.shape == 'arrowhead' then shape.shapeAngle=tostring(round(lineAngle,0))..'deg' end -- line angle for arrowhead -- debugmsg('text angle is now '..shape.textNG..'with arrow at '..lineAngle) end local function makeClipPath(result,row,shape,outline,tshape) --tshape is a flag to show if the tooltip (title=) is wanted -- return the text css div code to position and draw a shape occupying a specified clippath if string.find(shape.shape, 'svgpath,') and #shape.shape > 9 then pathshape.svgpath=string.sub(shape.shape,9) shape.shape='svgpath' end if not pathshape[shape.shape] then debugmsg(mw.addWarning('shape'..row.code..' = '..shape.shape..'. The shape name is not defined')) return end local w,h,r=getsize(shape.shapeSize) if shape.shape == 'arrowhead' then -- enlarge arrowheads, as they only go to the middle of the notional shape-size w=w*2.5 h=h*2.5 end if string.match(shape.shape,"circle") or string.match(shape.shape,"square") then h=w end -- use ellipse and box for stretched shapes local shifth,shiftv = 0,0 if string.match(shape.shape,"triangle") then shiftv,shifth =tshift(shape.shapeAngle) end if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) or 0 w=w+p*2 h=h+p*2 end table.insert(result,"<div ") if tshape then -- Add tooltip if needed table.insert(result," title=\""..row.title.."\" ") end table.insert(result,"style=\"display:inline-block; position: absolute; background-color:") if outline then table.insert(result,getColor(shape.outlineColor)) -- fill with outline colour, to make a 'base layer' else table.insert(result,getColor(shape.shapeColor)) end table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;"..pathshape[shape.shape].."&#39;) ") -- adds the required clippath data from the table of pathshape string literals table.insert(result,"; top:"..tostring(row.gridy - 10 + h*shiftv).."px") table.insert(result,"; left:"..tostring(row.gridx - 10 + w*shifth).."px") table.insert(result,"; width:20px") -- needs to be a path within a 20px20px box, and then rescales using size values to match other shape sizes table.insert(result,"; height:20px; transform:") if shape.shapeAngle ~= "0deg" then table.insert(result," rotate("..shape.shapeAngle..")") end table.insert(result," scale("..tostring(w/16)..", "..tostring(h/16)..")") table.insert(result,"\"></div>") end local function makeImage(result,row,shape) local w,h,r=getsize(shape.shapeSize) local image=shape.shapeFile if not image or image=='' then image='Red pog.svg' end local imagediv=mw.html.create ('div') imagediv:css('position', "absolute") if shape.shapeAngle ~= "0deg" then imagediv:css('transform',"rotate("..shape.shapeAngle..")") end imagediv :css('top', (row.gridy+2 + math.min(h/2-12,0) - h/2).."px") --File seems to adjust pos for small images :css('left', (row.gridx-1-w/2).."px") :css('background-color', "transparent" ) :css('color','inherit') :wikitext('[[file:'..image..'|'..tostring(w+2)..'px|alt='..(row.title or '')..'|link=]]') table.insert(result,tostring(imagediv)) end local function makePhotoPanel(result,row,shape) local h=row.ppheight table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;' ) table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') table.insert(result, 'width: '..tostring(row.ppwidth)..'px; height: '..tostring(h)..'px; border-radius: 2px; color:inherit;') table.insert(result, 'background-color: #E8E8D6; outline: 2px solid '..getColor(shape.textCL)..'; box-shadow: 2px 2px 4px #33203335;"></div>') if row.photoImage and row.photowidth >0 then table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;') if row.labelPos=='left' or string.find(row.labelPos,'west') then table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') row.dx=row.dx+row.photowidth/2 else table.insert(result, 'left: '..tostring(row.gridx+row.dx + (row.ppwidth-row.photowidth) - row.ppwidth/2)..'px;') row.dx=row.dx-row.photowidth/2 end table.insert(result, 'background-color:transparent; color:inherit; border-radius: 2px;">') table.insert(result,'[[File:'..row.photoImage..'|x'..tostring(h)..'px|File:'..row.photoImage..']]</div>') row.labelPos='center' end end local function makePanelText(result, row, shape) local w,h,r=getsize(shape.shapeSize) local ty=tonumber(string.match(shape.textSZ or '11',"%d+") ) table.insert(result,'<div style="position:absolute; line-height: 120%; font-size: '..shape.textSZ..'; color:'..getColor(shape.textCL)..';') if row.labelPos == 'left' or string.find(row.labelPos,'west') then table.insert(result,'top: '..tostring(row.dy+row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+3)..'px; text-align: left; width:'..tostring(w)..'px;') elseif row.labelPos == 'right' or string.find(row.labelPos,'east') then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+w-3)..'px; text-align: right; width: max-content; transform: translateX(-100%);') elseif row.labelPos == 'top' or row.labelPos== 'north' then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') elseif row.labelPos == 'bottom' or row.labelPos=='south' then local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*1.1 table.insert(result,'top: '..tostring(row.gridy+w-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') else -- center or centre local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*0.6 table.insert(result,'top: '..tostring(row.gridy+(h/2)-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') end if shape.textSP and shape.textSP ~='0px' then table.insert(result,"letter-spacing: "..shape.textSP..';') end if shape.textLH and shape.textLH ~='120%' then table.insert(result,"line-height: "..shape.textLH..';') end table.insert(result,"vertical-align: middle;\">"..row.labelText.."</div>") end local function makeTextItem(result, row, shape, align, tlink, textItem, dotItem) local w,h,r=getsize(shape.shapeSize) table.insert(result,"<div ") if row.title ~= "" then table.insert(result," title=\""..row.title.."\" ") end local ty,bry,linkoffset = 0,0,0 local compy=0 local lh=tonumber(string.match(shape.textLH or '120',"%d+"))/100 if dotItem==1 or (dotItem==2 and row.posType~='n-line') then -- if there is a dotTag in the middle of the shape then use the tag settings ty=tonumber(string.match(shape.tagSize,"%d+")) or 0 table.insert(result,"style=\"position:absolute; line-height: 120%; top: "..tostring(row.gridy-ty*0.62)) table.insert(result,"px; left: "..tostring(row.gridx).."px; width: fit-content; ") table.insert(result,"text-align: center; color: "..getColor(shape.tagColor).."; background-color: transparent;") local trf="" if shape.tagAngle ~="0deg" then trf=" rotate("..shape.tagAngle..")" end table.insert(result, "transform: translateX(-50%)"..trf.."; font-size: "..shape.tagSize..";") if shape.tagSpacer~='0px' then table.insert(result, "letter-spacing:"..shape.tagSpacer..";") end else -- or add tfx settings for left, right or center align, colors, backgrounds, border-outline table.insert(result,'style="position:absolute; ') if dotItem==2 then -- dotTag is out at x,y so 85% ty=tonumber(string.match(shape.tagSize or '0',"%d+")) table.insert(result,'font-size: '..shape.tagSize..'; padding:0px 2px;line-height: 85%; top: '..tostring(row.dy+row.gridy-ty*0.52)) else -- it is labelText, so use textLH or 120% ty=tonumber(string.match(shape.textSZ or '11',"%d+")) if row.labelPos=='northwest' or row.labelPos=='northeast' then compy=-ty elseif row.labelPos=='southeast' or row.labelPos=='southwest' then compy=ty/2 end if row.labelPos and not (row.labelPos== 'auto' or row.labelPos=='') then bry=(select(2, string.gsub(row.labelText,"<br>", ""))*lh) -- is it a multiline text? expand by line-height /120%? if row.posType == 'with-line' then bry=0 end if row.labelPos=='bottom' or row.labelPos == 'south' then bry = 0 -- and shift by none, all or half elseif row.labelPos=='top' or row.labelPos == 'north' then if shape.shape=='image:' then bry= 1 + math.min(w/2-10,0)-bry*ty else bry= -bry*ty+2 end else bry=-bry*(ty/2 * lh) end end if row.posType == 'photo-panel' then bry=bry+3 end table.insert(result,'font-size: '..shape.textSZ..'; padding:0px 3px;line-height: '..shape.textLH..'; ') table.insert(result,'top: '..tostring(row.dy+row.gridy+compy+bry-ty*lh/2)) end table.insert(result,"px; left: "..tostring(row.dx+row.gridx).."px; color: "..getColor(shape.textCL).."; ") table.insert(result,"width: max-content; ") local trf="" if shape.textNG ~="0deg" then trf="rotate("..shape.textNG..")" end if shape.textOL~="0px" then table.insert(result,"background-color: "..getColor(shape.textBG).."; ") table.insert(result,"border: "..shape.textOL.." solid "..getColor(shape.textCL).."; border-radius:6px;") else table.insert(result,"background-color: transparent;") end if row.labelPos=="right" or string.find(row.labelPos,'east') then table.insert(result,"text-align: left; ") linkoffset=w if shape.textNG ~="0px" then table.insert(result,"transform-origin: left; transform: rotate("..shape.textNG.."); ") end elseif row.labelPos=="left" or string.find(row.labelPos,'west') then if shape.textNG ~="0px" then table.insert(result,"transform-origin: right;") end linkoffset=-w table.insert(result,"text-align: right; transform: translateX(-100%) "..trf.."; ") else table.insert(result,"text-align: center; transform: translateX(-50%) "..trf.."; ") end table.insert(result,'font-weight: normal; line-height: '..shape.textLH..'; letter-spacing:'..shape.textSP..'; vertical-align: bottom;') end if string.find(shape.textAT or '','bold') then textItem='<b>'..textItem..'</b>' end if string.find(shape.textAT or '','italic') then textItem='<i>'..textItem..'</i>' end if shape.textOL=='0px' and shape.textBG~='transparent' and dotItem==0 then table.insert(result,'\"><span style=\"background-color: '..getColor(shape.textBG)..'; color:inherit;\">'..textItem..'</span></div>') else table.insert(result,"\">"..textItem.."</div>") end if tlink~='' and not string.match(row.param1 or "","nolink") then table.insert(result,makeLinkBox(row.gridx+row.dx+linkoffset, row.gridy+row.dy+bry, 16, row.dotTag..' '..row.title,tlink)) end end local function getshapetable(row,shape) -- Construct CSS divs for a dot from shape and map data local result={} local w,h,rndg=getsize(shape.shapeSize) local tshape,tlink=checkfortooltip(row.title,row.dx,row.dy,row.dotTag,row.dlink,string.match(row.param1 or "","nolink") ) local align=row.labelPos or '' local offsetx,offsety=0,0 local ty=tonumber(string.match((shape.tagSize or 9),"%d+" )) if row.labelText and row.labelText~='' then ty=tonumber(string.match((shape.textSZ or 11),"%d+" )) else align='center' end -- it is just for dotTag, so justify center -- identify align value and extend offsets local widthzone,heightzone = (w/2)+1,(h/2)+1 local theta,r = math.deg( math.atan2(row.dy, row.dx)) , math.sqrt(row.dx^2 + row.dy^2) if align=='auto' or align=='' then if (theta < -112 or theta > 112) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "left" offsetx=1 elseif (theta > -68 and theta < 68) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "right" offsetx=-1 elseif theta <0 then offsety=ty/2-1 -- bottom else offsety=0-ty/2+1 -- top end elseif align=='left' or string.find(align,'west') then row.dx=row.dx - w/2 widthzone=4 offsetx=-1 elseif align=='right' or string.find(align,'east') then row.dx=row.dx + w/2 widthzone=4 offsetx=1 elseif align=='top' or align=='north' then if string.find(shape.shape,'curve') then row.dy=row.dy-(h/2)-12 heightzone=4 offsety=ty/2-1 else row.dy=row.dy - h-2 heightzone=4 offsety=ty/2-1 end elseif align=='bottom' or align=='south' then if string.find(shape.shape,'curve') then row.dy=row.dy+(h/2) heightzone=4 offsety=ty/2-1 else row.dy=row.dy + h+2 heightzone=4 offsety=0-ty/2-1 end end if shape.textEW ~= "0px" and not string.match(row.param1 or "","noline") then -- debugmsg('r-(w/2) > widthzone r-w/2='..tostring(r-(w/2))..', widthzone='..widthzone..', label='..(row.labelText or '')..', postype='..(row.posType or '')) makeLineTo(result, row.gridx+1, row.gridy-1, row.gridx+row.dx+offsetx, row.gridy+row.dy+offsety, shape.textEW, shape.textES,shape.textEC,1) -- debugmsg('n-line for '..row.code..':'..tostring(row.gridx+1)..', '..tostring(row.gridy-1)..', '..tostring(row.gridx+row.dx+offsetx)..', '..tostring(row.gridy+row.dy+offsety)..', with width '..tostring(shape.textEW)..'px '..(shape.textES or 'no style')..' and color '..shape.textEC) end if row.posType and row.posType=='mark-line' then if row.gridx2 and row.gridy2 then makeLineTo(result, row.gridx, row.gridy, row.gridx2, row.gridy2, tostring(row.mlWidth)..'px', row.mlStyle, getColor(shape.textEC),row.mlGap,shape) -- debugmsg('mark-line line drawn from '..row.code..' with width '..tostring(row.mlWidth)..'px '..(row.mlStyle or 'no style')..' and color '..getColor(shape.textEC)) end end if w ~= 0 then if shape.shape=='itriangle' then shape.shape='triangle' shape.shapeSize=tostring(w)..'px'..tostring(w/2)..'px' end if shape.shape=="triangle" then if shape.outlineWidth ~= "0px" then makeTriangle(result,row,shape,true,false) -- larger triangle to give the outline, if required end makeTriangle(result,row,shape,false,tshape) -- smaller triangle to fit over the top elseif shape.shape=="square" or shape.shape=="box" or shape.shape=='panel' then makeSquare(result,row,shape,tshape) elseif shape.shape=="circle" or shape.shape=="ellipse" then makeCircle(result,row,shape,tshape) elseif string.find(shape.shape,'image:')==1 then makeImage(result,row,shape) elseif shape.shape=="rulea" or shape.shape=='rule' then makeRuleA(result,row,shape) elseif shape.shape=="curvea" or shape.shape=="curvec" then makeCurveA(result,row,shape) else -- use a pathshape clipPath if shape.outlineWidth ~= "0px" then makeClipPath(result,row,shape,true,false) -- larger path-shape to give the outline, if required end makeClipPath(result,row,shape,false,tshape) end end if row.ppwidth and row.ppwidth>0 then makePhotoPanel(result,row,shape) end if shape.shape=='panel' and row.labelText then makePanelText(result, row, shape) else if row.dotTag and row.dotTag ~= "" then -- there is a dotTag if (row.dx==0 and row.dy==0) and w>0 then -- it is on the dot so if dotsize is not 0 any label is ignored makeTextItem(result, row, shape, align, tlink, '<b>'..row.dotTag..'</b>', 1) else if row.labelText and row.labelText~='' then -- tag and label both used makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.labelText, 0) else makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.dotTag, 2) -- tag is ouside the dot end end else if (row.labelText and row.labelText~='') then -- just the label. No tag makeTextItem(result, row, shape, align, tlink, row.labelText, 0) end end end if tlink and tlink~='' then table.insert(result,makeLinkBox(row.gridx, row.gridy,w+3,row.dotTag..' '..row.title,tlink)) end return table.concat(result) end local function getmapframecontent(args,use) local result, comma= {}, '' local propertyTable= {} local color=splitItem(string.lower( args['map-data-color'] or 'dark orange'), 2) if use == 'basemap' then table.insert(result, '[') propertyTable.stroke = "#000000" propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') if color[2] then propertyTable['stroke-opacity'] = tonumber(color[2] or '20')/100 end else propertyTable.title = args['map-data-text'] or args['map-data'] or '' propertyTable.stroke = getColor(color[1]) propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') propertyTable['stroke-opacity'] = tonumber(color[2] or '100')/100 end if args['map-data-inverse'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geomask', ids = args['map-data-inverse'], properties = {title = (args['map-data-text'] or ''), stroke = '#555555', fill = '#555555', ['fill-opacity'] = 0.1, ['stroke-width'] = 1, ['stroke-opacity'] = 0.5 } } table.insert(result, mapJson) comma=', ' end if args['map-data-heavy'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-heavy'], properties = { title = args['map-data-heavy'], stroke = propertyTable.stroke, ['stroke-width'] = 9, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.2 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data-light'] and args['map-data-light'] ~= '' then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-light'], properties = { title = args['map-data-light'], stroke = propertyTable.stroke, ['stroke-width'] = 3, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.4 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data'] then propertyTable['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.2 local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data'], properties = propertyTable } table.insert(result, comma .. mapJson) comma=', ' end if args['map-wdqs'] then -- inserts a SPARQL wdqs query to find geopoints, geoshapes etc from wikidata local mapSparql = mw.text.jsonEncode{ type = 'ExternalData', service = args['map-wdqs-type'] or 'geopoint', query = args['map-wdqs'], } table.insert(result, comma .. mapSparql) -- debugmsg('mapSparql = ' .. mapSparql) comma=', ' end if args['map-raw'] then table.insert(result, comma .. args['map-raw']) end if use=='basemap' then table.insert(result,']') end return table.concat(result) end --eg | minilocator=filename,bottom right,132px153px, 38%,60%, 22px local function makeLocatorMap (args, result) local miniFile,pos,itemlist,miniW,miniH, miniX,miniY,miniBox, miniBH if args['mini-locator'] then pos,miniFile=extractItem(args['mini-locator']) -- first item is filename, in quotes if it includes commas itemlist=splitItem(pos,5) -- put items in a table filename removed, position,WpxHpx, x%y%,box pos=itemlist[2] or 'right' miniW,miniH=getsize(itemlist[3]) miniX=tonumber(string.match(itemlist[4] or '0','[%d]+'))*miniW/100 miniY=tonumber(string.match(itemlist[5] or '0','[%d]+'))*miniH/100 miniBox=tonumber(string.match(itemlist[6] or '0','[%d]+')) miniBox=miniBox*miniW/100 miniBH=miniBox * maplist.height/maplist.width elseif args['mini-file'] then miniFile = args['mini-file'] pos=string.lower(args.minimap or 'right') if pos=='file' then pos='right' end miniW,miniH = tonumber(args['mini-width'] or 60), tonumber(args['mini-height'] or 60) -- find top left corner of locator miniBox,miniBH=tonumber(args['minimap-boxwidth'] or '0'),0 -- firm up pos offsets for dot (with % or not) and boxsize if any, miniX, miniY=args['minipog-gx'],args['minipog-gy'] if not miniX then miniX=tonumber(args['minipog-x'] or '0') else miniX=tonumber(miniX)*tonumber(miniW)/100 end if not miniY then miniY=tonumber(args['minipog-y'] or '0') else miniY=tonumber(miniY)*tonumber(miniH)/100 end if args['minipog-gx'] then miniBox=miniBox*miniW/100 end miniBH=miniBox * maplist.height/maplist.width else return end local miniTop,miniLeft=0,1 if not string.find(pos,'top') then --only use top left, as link box is in top right or put bottom left or right miniTop=maplist.height+2-miniH if string.find(pos,'right') then miniTop=miniTop-15 -- to avoid (c) line miniLeft=maplist.width-1-miniW end end table.insert(result,'<div style="position: absolute; outline-width: 1px; outline-style: solid; outline-color: white;') table.insert(result,'top: '..tostring(miniTop)..'px; left:'..tostring(miniLeft)..'px; width:'..tostring(miniW)..'px;' ) table.insert(result,'background-color:transparent; color:inherit;">[[File:'..miniFile..'|'..tostring(miniW)..'px|File:'..miniFile..']]</div>') if miniX and miniX>0 then if args['minipog-y'] then miniY=miniY/1.04 end if args['minipog-x'] then miniX=miniX/1.04 end if miniBox<1 then table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-3-6)..'px; left:'..tostring(miniX+miniLeft-3)..'px;') table.insert(result, 'width: 6px; background-color:transparent; color:inherit;">[[File:Red pog.svg|6px|link=]]</div>') else table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-miniBH/2)..'px; left:'..tostring(miniX+miniLeft-miniBox/2)..'px;') table.insert(result, 'width: '..tostring(miniBox)..'px; height:'..tostring(miniBH)..'px; outline:1px solid #AA1205; background-color:#AA120522; color:inherit;"></div>') end end end local function makeArcText(args,result,nval) local items, itemlist='',{} local arcText='' if args['arc'..nval] then items=convertCoords (args['arc'..nval]) items,arcText=extractItem(items) -- first item is text, in quotes if it includes commas itemlist=splitItem(items,9) -- put items in a table: text, lat,lon,size,color,angle,gap,radius,ellipse if itemlist[4]=='' then itemlist[4]='12' end itemlist[4]=string.match((itemlist[4] or '12'),"[%.%-?%d]+") if itemlist[5]=='' then itemlist[5]='grey' end itemlist[6]=string.match((itemlist[6] or '0'),"[%.%-?%d]+") itemlist[7]=string.match((itemlist[7] or '0'),"[%.%-?%d]+") itemlist[8]=string.match((itemlist[8] or '0'),"[%.%-?%d]+") end if args['arc-coord'..nval] then local itemTab=splitItem(convertCoordsTrad (args['arc-coord'..nval]),2) maplist.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) maplist.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) else maplist.lat=tonumber(string.match(args['arc-lat'..nval] or itemlist[2] or '0',"[%.%-?%d]+")) maplist.lon=tonumber(string.match(args['arc-lon'..nval] or itemlist[3] or '0',"[%.%-?%d]+")) end local arcX,arcY=maptogrid(maplist,6) arcText = args['arc-text'..nval] or arcText local fontSize =tonumber(args['arc-text-size'..nval] or itemlist[4] or '12') local textColor=getColor(string.gsub(args['arc-text-color'..nval] or itemlist[5] or 'grey','[%s]+','') ) local arcAngle= tonumber((args['arc-angle'..nval]) or (itemlist[6]) or '45')-90 local arcRadius =tonumber(args['arc-radius'..nval] or itemlist[8] or '0.05') local arcGap = tonumber(args['arc-gap'..nval] or itemlist[7] or '1')* ( ( math.sin(8-math.rad(arcRadius))^8 )+0.4 )*( ( fontSize+6 )/15 ) arcRadius=450*arcRadius*0.75 local ellipseFactor=tonumber(args['ellipse-factor'..nval] or itemlist[9] or '1') local arcRotate =arcAngle+90 if arcGap<0 then arcRotate=arcAngle-90 end local latF=arcY - fontSize + (0-(math.sin(math.rad(arcAngle))) * arcRadius) local lonF=arcX - fontSize +(0-(math.cos(math.rad(arcAngle))) * arcRadius) local step=1 for codepoint in mw.ustring.gcodepoint( arcText ) do -- block step=1,#arcText do table.insert(result,'<div style="position: absolute;') local posY=tostring(round( (latF + (math.sin(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius)) *ellipseFactor,2))..'px;' local posX=tostring(round( (lonF + (math.cos(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius))/ellipseFactor,2))..'px;' table.insert(result,' top: '..posY..' left: '..posX..' transform: rotate( '..tostring(round(arcRotate +((step-1)*arcGap)),2)..'deg);') table.insert(result,'width:'..tostring(fontSize*2)..'px; text-align: center; background-color:transparent; color: '..textColor..';') table.insert(result,'vertical-align: baseline; font-size: '..tostring(fontSize)..'px;">'..mw.ustring.char(codepoint)..'</div>') step=step+1 end end local function makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) local item={} itemdescription=stripdivs(itemdescription or '') local templon=lon if lon > 180 then templon=lon-360 end --for hemisphere+ or -1 dots if lon < -180 then templon=lon+360 end -- use 'real' coordinates for geohack label, while retaining shifted coords for plot if itemcolor=='transparent' then itemcolor='white' end itemcolor=getColor(itemcolor) -- ensure no opacity, which breaks maplink if string.find(itemcolor,'#')==1 and #itemcolor>7 then itemcolor=string.sub(itemcolor,1,7) end table.insert(item, '{ "type": "Feature", "properties": {') table.insert(item, ' "title": "'..itemtitle..'",') table.insert(item, ' "description": "'..itemdescription) table.insert(item, ' ([https://geohack.toolforge.org/geohack.php?params='..tostring(lat)..';'..tostring(templon)) table.insert(item, '_dim:2000 '..tostring(lat)..','..tostring(templon)..'])",') table.insert(item, ' "marker-symbol": "-number-'..string.gsub(group,'%W','')..'", "marker-size": "medium", "marker-color": "'..itemcolor..'" },') table.insert(item, ' "geometry": {"type": "Point", "coordinates": ['..tostring(lon)..','..tostring(lat)..'] } }') return table.concat(item) end local function makeLegendBox(result,args) local legend ={} local line,count, maxWidth='',1,8 local item line,item=extractItem(args.legendBox or '') local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end legend.Text = a -- convert hats to line breaks line=splitItem(line,6) -- (text, size, poition,background color, text/outline color, param options) legend.Size=line[2] or '150px80px1px' legend.Pos=line[3] or '10px10px' legend.Background=line[4] or 'beigeground' legend.Color=line[5] or 'darkbrown' legend.Param= line[6] or '' local argnum,legendCount,titleHeight='1',1,0 if (legend.Text and legend.Text~='') then titleHeight=15+(13.4*(select(2, string.gsub(legend.Text,"<br>", "")))) end local legendLine,legendGroup,legendY={},{},{} while args['legendItem'..argnum] do -- assign legendLine, legendGroup, legendY for each dot line,legendLine[legendCount] = extractItem(args['legendItem'..argnum] or '') line=splitItem(line,3) legendGroup[legendCount]=line[2] or argnum if shapeList[legendGroup[legendCount]] then maxWidth=math.max(tonumber(string.match(shapeList[legendGroup[legendCount]].shapeSize or '10','[%d]+')),maxWidth) else maxWidth=math.max(tonumber(string.match(shapeList['1'].shapeSize or '10','[%d]+')),maxWidth) end maxWidth=maxWidth+1 if line[3] then legendY[legendCount]=tonumber(string.match(line[3],'[%d]+')) else legendY[legendCount] = 3+maxWidth*(legendCount-1)+titleHeight -- if (legend.Text and legend.Text~='') then legendY[legendCount]=legendY[legendCount]+15 end end legendCount=legendCount+1 argnum=tostring(legendCount) end local w,h,r=getsize(legend.Size or '') local x,y=getsize(legend.Pos or '') local div=mw.html.create ('div') div:css('position', 'absolute') div:css('outline', '1px solid'..getColor(legend.Color)) if r~=0 then div:css('border-radius',tostring(r).."px") end div :css('top', y.."px") :css('left', x.."px") :css('width', w.."px") :css('height', h.."px") :css('line-height','105%') :css('background-color', getColor(legend.Background) ) :css('color','inherit') if not string.find(legend.Param,'noshadow') then div:css('box-shadow', '2px 2px 4px #33203335') end div:tag( 'div' ) :css('position', 'absolute') :css('top','1px') :css('left', (w/2).."px") :css('width',(w-8)..'px') :css('text-align', 'center') :css('color', getColor(legend.Color)) :css('transform', 'translateX(-50%)') :css('font-size','11px') :wikitext(legend.Text) :done() for lct=1,legendCount-1 do --local t=legendGroup[lct] local shape=shapeList[legendGroup[lct]] or shapeList['1'] local row={} row.gridx=3+maxWidth/2 row.gridy=(legendY[lct] or 0) + 5 if shape.shape=='image:' then row.gridy=row.gridy+6 end row.dx=0 row.dy=0 local legendShape= getshapetable(row,shape) div:wikitext(legendShape) :tag( 'div' ) :css('position', 'absolute') :css('top',(legendY[lct] or 0)..'px') :css('left', (maxWidth+6)..'px') :css('width', (w-maxWidth-6)..'px') :css('text-align', 'left') :css('line-height','103%') :css('color', getColor(legend.Color)) :css('font-size','10px') :wikitext(legendLine[lct]) :done() end div:allDone() table.insert(result,tostring(div)) end local function getQset (qValue,pValue,argNumber,args) local result={} local sortType=string.lower(args['geo-list-sort'] or 'a') local testValue='0' if sortType == 'alpha' or sortType == 'a' or sortType == 'ah' or sortType == 'al' then testValue = mw.wikibase.getLabel(qValue) elseif sortType == 'value' or sortType == 'v' or sortType == 'vh' or sortType == 'vl'then if qValue==nil or pValue == nil then debugmsg(mw.addWarning((qValue or 'nil')..' Qnumber for '..(pValue or 'nil'))) else if mw.wikibase.getBestStatements(qValue,pValue)[1] then testValue = mw.wikibase.getBestStatements(qValue,pValue)[1].mainsnak.datavalue.value.amount or '0' if pValue == 'P2046' then testValue = convertToSqKm(testValue, mw.wikibase.getBestStatements(qValue,pValue)[1].mainsnak.datavalue.value.unit) end end end elseif sortType == 'north' or sortType == 'n' or sortType == 'nh' or sortType == 'nl' then if mw.wikibase.getBestStatements(qValue,'P625')[1] then testValue = mw.wikibase.getBestStatements(qValue,'P625')[1].mainsnak.datavalue.value.latitude or 0 end elseif sortType == 'west' or sortType == 'w' or sortType == 'wh' or sortType == 'wl' then if mw.wikibase.getBestStatements(qValue,'P625')[1] then testValue = mw.wikibase.getBestStatements(qValue,'P625')[1].mainsnak.datavalue.value.longitude or 0 end end result[1]=testValue result[2]=argNumber return result end function p._main ( args ) local result={} local frame=mw.getCurrentFrame() local dotTable={} local magVal,scaleVal = args.magnify,'' local origH,origW=maplist.height,maplist.width if magVal then --set up the values needed to magnify the top-right portion of the map magVal=tonumber(string.match(magVal or '0',"[%.%-?%d]+")) or 1 if magVal>1 and magVal <=2 then maplist.height= round(maplist.height/magVal,0) maplist.width=round(maplist.width/magVal,0) scaleVal='transform: scale('..magVal..') translateY('..tostring((origH-maplist.height)/2)..'px);' else magVal=1 end end -- set up the three nested div boxes (plus an extra if centered) to put the map plus title/caption area, in an appropriate frame on the page if args.float=='center' or args.float=='centre' then table.insert(result,'<div class="center"><div class="thumb tnone">') elseif args.float=='left' then table.insert(result,'<div class="thumb floatleft">') else table.insert(result,'<div class="thumb floatright">') end table.insert(result,'<div class="thumbinner" style="position: relative; top: 0px; right: 0px; width: '..(args.width or "400")..'px;">') if args.title then table.insert(result,'<div class="center" style="font-weight:bold">'..args.title..'</div>') end if magVal and magVal >1 and magVal<=2 then table.insert(result,'<div class="thumbinner noresize" style="display:block; position: relative; outline:0px; border:0px; padding:0px; background-color:transparent; color:inherit;') table.insert(result,'top: 50%; right: '..tostring(0-((origW-maplist.width))/2)..'px; ') table.insert(result,'height: '..origH..'px; width: '..maplist.width..'px; '..scaleVal..'">') else magVal=1 table.insert(result,'<div class="thumbinner noresize" style="position: relative; outline:0px; border:0px; padding:0px;') table.insert(result,'top: 0px; right: 0px; ') table.insert(result,'height: '..maplist.height..'px; width: '..maplist.width..'px ">') end --add any boundary Qvalues to map-data-light if requested if args['geo-region'] and (args['geo-boundaries'] == '1') then local qmaps=args['map-data-light'] or '' if qmaps ~='' and string.sub(qmaps,#qmaps) ~= ',' then qmaps=qmaps..',' end local geoRegion = args['geo-region'] -- or add base-page here local geoSubRegion = args['geo-sub-region'] or 'P150' if not mw.wikibase.isValidEntityId(geoSubRegion) then geoSubRegion = 'P150' end if mw.wikibase.isValidEntityId(geoRegion) then local ct=1 local qtemp while (mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct]) do -- debugmsg('table '..ct..' - '..mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id) qtemp = mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id or nil if qtemp then if mw.wikibase.entityExists(qtemp) then qmaps=qmaps..qtemp..',' -- see if there is a geocode boundary Qvalue to add else debugmsg(mw.addWarning('unable to add geocode value for '..mw.wikibase.getLabel..','..qtemp)) end end ct=ct+1 end end if qmaps and qmaps ~= '' then -- add back the created/expanded list of boundary Qvalues, and remove trailing comma args['map-data-light'] = string.sub(qmaps,1,#qmaps-1) end end -- Create the basemap using mapframe local mapframecontent=getmapframecontent(args,'basemap') table.insert(result, frame:extensionTag{ name ='mapframe', content=mapframecontent, args={width=tostring(maplist.width), height=tostring(maplist.height), zoom=tostring(maplist.zoom), longitude=tostring(maplist.lonbase), latitude=tostring(maplist.latbase), mapstyle=maplist.mapstyle, frameless=true } } ) --Add coverall box to block the unhelpful links from mapframe - which wouldn't include all the dots. Reinstate some links to osm and wikimedia table.insert(result,'<div style="position: absolute;width:'..tostring(maplist.width)..'px; height:'..tostring(maplist.height)..'px;') table.insert(result,'top:0px;left:0px;background-color:#FFFFFF00; color:inherit;"></div>') --Add replacent hover-links for OpenStreetMap and maps terms and conditions table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-13)..'px; width: 12px; height: 12px">') table.insert(result,'[[file:Transparent.svg|12px|link=https://www.openstreetmap.org/copyright|'..aboutOSM..']]</div>') table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-110)..'px;') table.insert(result,'width: 12px; height: 12px background-color: transparent; color:inherit;">') table.insert(result,'[[file:Transparent.svg|12px| link=https://foundation.wikimedia.org/wiki/Policy:Maps_Terms_of_Use|'..termsOfUse..']]</div>') -- Add scale-line if not args.scalemark or args.scalemark~='0' then local top=maplist.height-42 local left=maplist.width-61-(tonumber(args.scalemark or '1')) local minipos=string.lower(args.minimap or '') -- scalemark gets pushed left if it would be behind the minimap if minipos~='' and not(string.find(minipos,'left') or string.find(minipos,'top') ) then local offset=tonumber(args.scalemark or '1')-tonumber(args['mini-width'] or '60') if offset<1 then left=maplist.width-61-tonumber(args['mini-width'] or '60') end end if maplist.width-left >216 then top=top+14 end -- shunt scaleline down if it is beyond the copyright stuff local scalek,scalem=getScale(maplist.zoom, maplist.latbase, magVal) local magReduce='' table.insert(result,"<div style=\"display:inline-block; position: absolute; background-color: #111111") table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;M0,8 l0,4 l20,0 l0,-4 l-0.3,0 l0,3.7 l-19.4,0 l0,-3.7 z&#39;) ") table.insert(result,"; width:20px") -- path is a 20px20px box, and then rescales if magVal==1 then table.insert(result,"; top:"..tostring(top-1).."px") table.insert(result,"; left:"..tostring(left+16).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5)..", "..tostring(1.5)..")") else -- shrink the scalemark to compensate for magnification table.insert(result,"; top:"..tostring(top-(magVal*0.28)).."px") table.insert(result,"; left:"..tostring(left+(16*(magVal*1.15))).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5*(1/magVal))..", "..tostring(1.5*(1/magVal))..")") magReduce= 'scale('..tostring(1/magVal)..')' end table.insert(result,"\"></div>") table.insert(result,'<div style="position: absolute; top: '..tostring(top)..'px; left: '..tostring(left+47)..'px; font-size: 9.5px; line-height: 126%; width: fit-content;') table.insert(result,'color: #444433; background-color: transparent; text-align: right; transform: '..magReduce..' translateX(-100%);">'..scalek..'<br>'..scalem..'</div>') end --Set up the shapeList and dotList tables, to provide data to go on the map local sgNumbers,sgSortable={},{} --s1,s2 sgNumbers["1"]="1" if args.useFormatStyle and args.useFormatStyle=='shortstyle' then shapeList=ParseShapeTypes (shapeList,args,"1") for argindex,argv in pairs(args) do -- build a list of all the numbered sg's that have been used if string.find(argindex,"sg[a-f,n%d]+") == 1 then -- only look through the sga,sgb, sgc,sgd,sge,sgf and sgn args local x=string.match(argindex,"[%d]+") -- find its number if x and not sgNumbers[x] then sgNumbers[x]=x end -- only add if not already found end if string.find(argindex,"sg[a-f]H") == 1 and highlightNum then highlightOption=true end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form for k,v in pairs(sgSortable) do -- work through the sorted list, parsing each set of sg's in turn, from 1 upwards shapeList=ParseShapeTypes (shapeList,args,v) end if highlightOption==true then shapeList=ParseShapeTypes (shapeList,args,'H') end end local dotList,dotresult,dotItemTable,dotGroupList={},{},{},{} if (not args.useFormatStyle) or args.useFormatStyle=='standardstyle' then local dotmax=0 local qset={} for indx,val in pairs(args) do if string.match(indx,'mark%-coord[%d]+') or string.match(indx,'lat[%d]+') or string.match(indx,'geo%-data[%d]+') then dotmax=math.max(dotmax, tonumber(string.match(indx,"[%d]+"))) if string.match(indx,'geo%-data[%d]+') then local argnum=string.sub(indx,9,#indx) or '' if args['mark-title'..argnum] == 'none' or (string.find(args['mark-title'..argnum] or 'x','nolist')== 1) then -- debugmsg('sort values ignored '..argnum..', '..args['mark-title'..argnum]) else qset[val]=getQset (val, (args['geo-data-type'] or 'P1082'), argnum, args) end end end if (indx=='shapeH') or (indx=='shape-colorH') or (indx=='shape-outlineH') or (indx=='label-colorH') then highlightOption=true end end local qlist=args['geo-data-list'] if qlist then if string.sub(qlist,#qlist) ~= ',' then qlist=qlist..',' end else qlist='' end local geoRegion = args['geo-region'] -- or add base-page here local geoSubRegion = args['geo-sub-region'] or 'P150' if not mw.wikibase.isValidEntityId(geoSubRegion) then geoSubRegion = 'P150' end if (args['find-geo-dots'] or '1') ~= '0' and geoRegion and mw.wikibase.isValidEntityId(geoRegion) then local ct=1 local qtemp while (mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct]) do -- debugmsg('table '..ct..' - '..mw.wikibase.getBestStatements(adminRegion,adminSubRegion)[ct].mainsnak.datavalue.value.id) qtemp = mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id or nil if qtemp then qlist=qlist..qtemp..',' -- add subregion Qvalue to the list end ct=ct+1 end end if qlist and #qlist>1 then -- create a table from the csv list local _, c = qlist:gsub(",","") local qtab=splitItem(qlist,c) for xd,yd in pairs(qtab) do yd=string.upper(yd) if qset[yd] == nil and mw.wikibase.isValidEntityId(yd) then dotmax=dotmax+1 qset[yd]=getQset (yd, (args['geo-data-type'] or 'P1082'), tostring(dotmax), args) args['geo-data'..tostring(dotmax)] = yd -- debugmsg(dotmax..' = '..(string.upper(yd) or 'Q Not found')..': '..(mw.wikibase.getLabel(yd) or 'label Not found')..': '..(mw.wikibase.getSitelink(yd) or 'link Not found')) elseif not mw.wikibase.isValidEntityId(yd) then debugmsg(mw.addWarning(yd..' is not a [[wikidata]] page. geo-data-list needs comma-separated Qvalues' )) else debugmsg(mw.addWarning(yd..' is duplicate')) end end end -- qset contains set of Qvalues and can be from wikidata geo-region, list of geo-data-list Q values or individual geo-data1= etc local ts = {} -- table to hold the keys local useQset=false for ky, val in pairs(qset) do table.insert(ts, {key = ky, value = val[1], num=val[2]}) useQset=true end if useQset then local order = string.lower(args['geo-list-sort'] or 'a') -- sort the list from previously stored sortkey, either Lowest or Highest first if string.find(',al,vl,nl,wl,west,w,alpha,a,' , ','..order..',') then table.sort(ts, function (a,b) return lessthan(a.value, b.value) end) else table.sort(ts, function (a,b) return morethan(a.value, b.value) end) end for ky,val in ipairs(ts) do -- assign values from sorted list to numbered parameter args['numbered'..val.num] = ky -- The numbered parameter becomes the value for each dot that has a geo Qvalue, from manual, list or wiki sources. end end dotItemTable=tradstyleParseShapes(args,dotItemTable,dotmax) for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) or args['geo-data'..x] then sgNumbers[x]=x -- add it to the list end end else for indx in pairs(args) do if string.match(indx,'dot[%d]+') then table.insert(dotList,indx) end --add the index name for dot1, dot2 etc to dotList end table.sort(dotList,morethan) for indx,dotName in pairs(dotList) do dotresult=ParseData(args,string.match(dotName,'[%d]+') ) -- using each dot number, assign the settings for each dot to a dotresult item line table.insert(dotItemTable,dotresult) -- and store that item line within the dotItemTable end end for arcVal = 65,91 do -- check through args looking for any arcs local arcLetter=string.char(arcVal) if args['arc-text'..arcLetter] or args['arc'..arcLetter] then makeArcText(args,result,arcLetter) end end local dotdivs='' local ddots=0 if dotItemTable[1] then local ddots=(dotItemTable[1].lat or 0)+(dotItemTable[1].lon or 0) end local fgroup='F'..tostring(maplist.latbase+maplist.lonbase+ddots ) local FullscreenList={} local addcomma='' for i,dotitem in pairs(dotItemTable) do -- working throug each dot item, merge the dot and shape values into a full set of css text local dotgroup= dotitem.group or "0" if dotitem.posType=='mark-line' and dotitem.markDest then --find destination xy values for any mark-lines for n,v in pairs(dotItemTable) do if v.code == dotitem.markDest then dotitem.gridx2=v.gridx dotitem.gridy2=v.gridy break end end end local qtype=dotitem.group -- find which shape group each dot has been assigned --debugmsg('dotgroup='..qtype..', sg='..(sgNumbers[qtype] or 'nil')..' , shapeList='..shapeList[qtype].shape) if not sgNumbers[qtype] then qtype="0" end --shapeList[dotitem.group] will give access to the shape values for that dot if highlightNum==dotitem.code and highlightOption==true and shapeList['H'] then table.insert(result, getshapetable(dotitem,shapeList['H'])) else table.insert(result, getshapetable(dotitem,shapeList[qtype])) -- Add the actual css instructions for each dot end if shapeList[dotgroup] and not string.find((dotitem.param1 or ''),'nomap') then -- only add if not excluded with 'nomap' labelText local ftext='' if dotitem.dotTag~='' and not string.match(dotitem.labelText or '','[%d]') then ftext=stripdivs(dotitem.dotTag or '')..' <br>' end if (dotitem.labelText ~= ftext) and dotitem.dotLink =='' then ftext=ftext..' '..stripdivs(dotitem.labelText)..'<br>' end if (dotitem.dotLink) and (dotitem.dotLink ~='') then ftext=ftext..dotitem.dotLink..'<br>' end if dotitem.imageName then ftext=ftext..'[[File:'..dotitem.imageName..'|250px]]' end table.insert(FullscreenList,1, makeFullscreenItem (string.gsub(ftext,"[\n]+"," "), dotitem.info,round(dotitem.lat,5),round(dotitem.lon,5),fgroup,shapeList[dotgroup].shapeColor)..addcomma ) addcomma=', ' end -- makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) -- Always add to start of list, to reverse the sequence, and separate with commas except for first item, which is now at the end end if args.legendBox then makeLegendBox(result,args) end if args.minimap or args['mini-locator'] then makeLocatorMap(args,result) end -- add tag link and details for fullscreen version addcomma='' if (mapframecontent or '[]') ~= '[]' then addcomma=',' end mapframecontent=getmapframecontent(args,'fullscreen') local contentstart='[ '..mapframecontent..addcomma..'{ "type": "FeatureCollection", "features": [ ' --extra features after first square bracket local contentend=' ] } ]' table.insert(result, '<div style="position: absolute;top: 9px;left: '..tostring(maplist.width-34)..'px">') table.insert(result, '<div style="color: white; opacity:100; font-size: 19px; font-weight:normal; text-align: left;">') table.insert(result, frame:extensionTag{ name ='maplink', content=contentstart..table.concat(FullscreenList)..contentend, args={zoom=tostring(maplist.zoom+1), class='no-icon', frameless='1', latitude=tostring(maplist.latbase), longitude=tostring(maplist.lonbase), --add invisble 'en-spaces' for tooltip text='<div title="'..fullscreenlinktext..'">&nbsp;&nbsp;&nbsp;</div>'} } ) table.insert(result,'</div></div>') -- end of maplink ----- -- add closing div for main map table.insert(result,'</div>') -- collate caption material to go in the outer div class if captionTable ~= '' and (not args['auto-caption']) then args['auto-caption']='on' end --default to include list if using geo values local autocaption=string.lower(args['auto-caption'] or 'no') local autoOff=autocaption:match("(%w+)(.*)") -- select the first word in autocaption and see if it is a negativeAnswer) if negativeAnswer[autoOff] then captionTable='' end if args.caption or (not negativeAnswer[autoOff]) then table.insert(result,'<div class="thumbcaption" style="text-align:left">') if args.caption then table.insert(result,args.caption) if captionTable ~= '' then table.insert(result,'<hr>') end end end local columns=tonumber(autoOff:match("[%d]+") or '1') if columns>1 then columns=round(maplist.width/(columns*17), 0) end -- convert from em to px for historical reasons --for k in pairs(dotList) do capchk=capchk..(args["dotlink"..k] or '') end local capchk=nil local captionList = {} for key, value in pairs(dotItemTable) do -- only add an autocaption line if there is both a dotTag and a dotLink line available and it is not marked as nolist if value.dotTag and value.dotTag~='' and (not string.find(value.param1 or '','nolist')) and string.gsub(value.dotLink or '',"%s+","")~='' then table.insert(captionList, {key = key, value = value}) if captionTable == '' then capchk=true else -- add the next line of content to the html table capchk=false end end end if captionTable ~= '' then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) for k,v in pairs(captionList) do local nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag local ngrp=v.value.group if captionTableText[ngrp] then -- debugmsg('table text for'..v.value.dotTag..', '..captionTableText[v.value.group][1]..', '..captionTableText[v.value.group][2]..', '..captionTableText[v.value.group][3]..' ('..captionTableText[v.value.group][4]..')') local c1,c2 c1,c2=checkColors(shapeList[ngrp].shapeColor) local cDot='<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;' cDot=cDot..'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div>' captionTable = htmlTableLine(captionTable,cDot,captionTableText[ngrp][1], captionTableText[ngrp][2], captionTableText[ngrp][3], captionTableText[ngrp][4]) end end end if capchk and (not negativeAnswer[autoOff]) then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) -- local myDivision = string.gsub((args.toggletext or toggletext), "%s+", "") if string.find(autocaption,'collaps') then table.insert(result,'<div class = "wikitable mw-collapsible ') if string.find(autocaption,'collapsed') then table.insert(result,'mw-collapsed') end table.insert(result,'" style="width:100%; line-height: 17px; border-style:none; text-align: center"><b>'..(args.toggletext or toggletext)..'</b>') end if string.find(autocaption, 'columns=') then columns=string.match(autocaption,'[%d]+',string.find(autocaption, 'columns=') ) end table.insert(result,'<div class="mw-collapsible-content" style="column-count:'..columns..'; column-rule:solid 1px;text-align:left;padding-top:5px">') table.sort(dotList,lessthan) local nval,ngrp='','0' for k,v in pairs(captionList) do nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag ngrp=v.value.group or '0' if v.value.dotLink and v.value.dotLink~='' and nval and nval~='' then local c1,c2 if nval==args.highlight then c1,c2=checkColors(shapeList['H'].shapeColor) else c1,c2=checkColors(shapeList[ngrp].shapeColor) end table.insert(result,'<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;') table.insert(result,'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div> '..v.value.dotLink..'<br>') end end table.insert(result,'</div>') -- end for caption-content div if string.find(autocaption,'collaps') then table.insert(result,'</div>') end -- end for toggle frame end if captionTable ~= '' then table.insert(result,captionTable..'</table>') end if args.caption or (not negativeAnswer[autoOff]) then table.insert(result,'</div>') end -- end for whole caption frame table.insert(result,'</div></div>') -- outer two frames if args.float == 'center' or args.float=='centre' then table.insert(result,'</div>') end if args['show-new-format'] == 'hints' then -- provide a 'format hint panel' in the 'Preview Box' local w="<small>Below are some template hints for the 'sga' compressed version, " w=w..'{{tl|OSM Location dots}}. It can use these, instead of the more verbose {{tl|OSM Location map}} parameter format. ' w=w..'Data is divided between a "ShapeGroup" and the "Dots", so that a single shapeGroup can be used for multiple dots on the map. ' previewMsg(w..'(nb. the "group" value can be the number or an assigned name of a shapeGroup)</small>') w='<small>{{tl|OSM Location dots}}: | dot(n)=group,lat,lon,dotTag | ' w=w..'dotlink=link/tooltip | dotlabel=label,position,dx,dy,param1,info | dotpic=filename <br>' w=w.."(nb. param1 options include 'nolink' 'nolist' 'nomap' 'hemisphere-1' 'hemisphere+1', 'noline' - quotes not required, separate with spaces).<br>" w=w..'| sga = Shape,Sizepx,Color,Angledeg | sgb= OutlineWidth,Color,Style | sgc=TextSize,Color,Angle,bold italic <br>' w=w..'| sgd=TagSizepx,Color,Spacer,Angledeg | sge=LineWidth,Color,Style | sgf=TextSpacingpx,LineHeight%,Outlinepx,backgroundColor<br>' w=w..'| sgn=Name (optional, to assign a meaningful name) | sgp=Parent (can be the name or number of the parent shapeGroup. ' previewMsg(w..'Each shapegroup will inherit values from a parent, stretching back to "sga1" and its default values.)</small>') if #pmsg then local dbg={} for i,x in pairs(pmsg) do table.insert(dbg,x..'<br>') end dbg=mw.addWarning(table.concat(dbg)) table.insert(result, dbg) end end if args.coordtest then debugmsg(mw.text.nowiki(args.coordtest)) end for i,x in pairs(msg) do table.insert(result, x..'<br>') end return table.concat(result) end function p.main(frame) local args = getArgs(frame) local itemTab={} maplist.width=tonumber(args.width) or 400 maplist.height=tonumber(args.height) or 300 if args.coord then itemTab=splitItem(convertCoordsTrad (args.coord),2) maplist.latbase=itemTab[1] maplist.lonbase=itemTab[2] else maplist.lonbase=tonumber(args.lon) or 5 maplist.latbase=tonumber(args.lat) or 0 end maplist.zoom=tonumber(args.zoom) or 1 visibleLinks=args.showlinks highlightNum=args.highlight if args.nolabels=='1' then maplist.mapstyle='osm' else maplist.mapstyle='osm-intl' end if maplist.width >= 400 and not(args['show-q-values']) then args['show-q-values'] = '1' -- default to on if wider than 400px elseif maplist.width < 320 then args['show-q-values'] = '0' end -- turn off Qvalus if frame is too narrow, even if requested return p._main(args) end return p 3d1rwotiu723z91tf01uwt6q04ej1me 204453 204452 2026-07-05T14:44:14Z HaxelKaszëba 17492 204453 Scribunto text/plain require('strict') local delink=require('Module:Delink').delink local getArgs = require('Module:Arguments').getArgs local p = {} local maplist={} local sgNames={} local highlightOption=false local highlightNum local visibleLinks local geoTotalPop = 0 local captionTable = '' local captionTableText = {} -- This module creates framed maps of anywhere in the world, at the required scale, and enables annotations, -- dots, shapes, lines and other ways to customise the area of the map being shown. It also provides a link -- to an interactive fullscreen version, which has locator dots instead of annotations and shapes. -- This is the 2025 successor module to a wiki-markup template version of 2024, which itself was a successor -- to the 'Graph'/VEGA driven template that was begun in 2016, until the Vega version was switched off in 2023. -- This module is called from template {{OSM Location map}}, which uses the same parameter formats as before. -- In addition it may at some point be possible to use a more concise parameter format using the template {{OSM Location dots}} -- In general the css output from the two formats will be identical, but the the concise version will allow bits of -- greater control over some of the settings. -- see the documentation on the two template pages for details of how to use the mapping features. -- If language customisation is needed, there are text items below that can be translated. Also see the color table -- below with details of how to add additional color names to allow localised alternatives. -- (Translating other language shape-types could be possible, but has not currently been contemplated. -- Parameter name translation would be harder but likely to be possible, ideally still retaining compatibility -- with template calls already written using English). -- -------------- the following text variables are used within the template, and can be translated as required --------------- -- -------------- you may wish to retain the English version as a comment, to keep track of the original phrase -------------- local negativeAnswer={no=1,'0'-1,off=1} local fullscreenlinktext = 'Click for interactive fullscreen map with links to nearby articles' local toggletext = 'List of numbered map items:' local termsOfUse = 'Maps: terms of use' local aboutOSM = 'About OpenStreetMaps' local yards = 'yds' local miles = 'miles' local metres = 'm' local km = 'km' local sqkm = 'sq.km' local year = 'year' local notFound = 'not found' local totalItems = 'Total' local sumOfDatapoints = 'sum of datapoints' local numberCol = 'No.' -- abbreviated for top of column of code numbers local wikidataLink = 'Wikidata page' local shapeList={} --This sets up the 'factoryDefault' shape group 0 shapeList["0"]={shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border of the shape outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items text settings for labels textSZ="11px", textCL="darkgrey", textNG="0deg", --sgf further text settings textSP="0px", textLH="120%", textOL="0px", textBG="transparent", --sgd items for dotTag text settings tagSize="10px", tagColor="white", tagSpacer="0px", tagAngle="0deg", --sge items for extension line to connect label to dot textEW="0px", textEC="darkgrey", textES="solid" } local colorList={} -- used by colorLookup to catch unsupported colors (eg 'LimeGreen'), to convert to generic version colorList['green']='hardgreen' -- it could also be added to to include alternative language equivelants, for a quick solution. colorList['red']='hardred' -- colorList ['source'] = target colorList['white']='white' -- converts any color that includes 'source' into its equivelent target colorList['blue']='hardblue' -- note, for translation you can add to this list, rather than replace it, colorList['brown']='brown' -- which would mean existing map definitions in english would also still work, alongside translated ones colorList['grey']='hardgrey' colorList['gray']='hardgrey' colorList['purple']='hardpurple' colorList['orange']='hardorange' colorList['leaf']='hardleaf' --for a more thorough translation, you can add all the variants of the colors as further CTB elements and hex values or redirects local CTB={} -- set up a table of color names (the CTB Color table index) and html hash colorhex values. CTB["paleblue"],CTB["softblue"],CTB["hardblue"],CTB["darkblue"]="#D6E1EC","#77A1CB","#4B77D6","#1c559e" CTB["palered"],CTB["softred"],CTB["hardred"],CTB["darkred"] = "#FCC6C0","#EC644B","#DB3123","#AA1205" CTB["palegreen"],CTB["softgreen"],CTB["hardgreen"],CTB["darkgreen"]= "#D2F0E5","#81AF81","#269F46","#0b7527" CTB["paleleaf"],CTB["softleaf"],CTB["hardleaf"],CTB["darkleaf"]= "#dff5c1","#b5e376","#8cc244","#679c21" CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"]= "#E8E8D6","#AAAA88","#777755","#444433" CTB["palegray"],CTB["softgray"],CTB["hardgray"],CTB["darkgray"]=CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"] CTB["palebrown"],CTB["softbrown"],CTB["hardbrown"],CTB["darkbrown"]="#FAF6ED","#CCB56C","#AD7F14","#754910" CTB["palepurple"],CTB["softpurple"],CTB["hardpurple"],CTB["darkpurple"]="#e0d1e6","#c784e0","#a029cf","#7a05a8" CTB["paleorange"],CTB["softorange"],CTB["hardorange"],CTB["darkorange"]="#ffedc2","#ffcf61","#EEB533","#e39f05" CTB["black"],CTB["white"],CTB["yellow"]="#000000","#FFFFFF","#FAF039" CTB["background"],CTB["paleground"],CTB["beigeground"]="#f9f6f2","#FEFEFA","#F5F5DC" CTB["beige"]=CTB["beigeground"] CTB["aqua"],CTB["teal"],CTB["fuchsia"] = "#00FFFF","#008080","#FF00FF" CTB["maroon"],CTB["olive"],CTB["navy"] = "#800000","#808000","#000080" CTB["lime"],CTB["limegreen"],CTB["aquamarine"] = "#00FF00","#32CD32","#7FFFD4" CTB["silver"],CTB["yellow"],CTB["orchid"] = "#800000","#FFFF00","#DA70D6" -- set up a table of predefined clip-paths local pathshape={} pathshape.squaredd = "M 19,1.25 l 0,18 -18,0 0,-18 18,0m-1,1 -16,0 0,16 16,0 0,-16m-1,1 0,14 -14,0 0,-14 14,0zm-1,1 -12,0 0,12 12,0 0,-12zm-1,1 0,10 -10,0 0,-10 10,0z" pathshape.squared = "M 18,2.5 l 0,15 -15,0 0,-15 15,0m-1,1 -13,0 0,13 13,0 0,-13zm-1,1 0,11 -11,0 0,-11 11,0z" pathshape.triangledd="M 0 20,20 20,10 0,0 20ZM1.5 19,10 1.7,18.5 19,1.5 19ZM3 18,17 18,10 3.8,3 18ZM4.5 17,10 5.4,15.4 17, 4.5,17ZM6 16,13.8 16,10 7.4z" pathshape.triangled ="M1,18 l 18,0 l -9,-18 l -9,18zm1.7,-1.1 l 7.3,-14.6 l 7.3,14.6 l -14.6, 0zm1.7,-1 l 11.0,0 l -5.5,-11 l -5.5,11z" pathshape.circledd = "M0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm0.8,0a9.2,9.2 0 1,1 18.4,0a9.2,9.2 0 1,1 -18.4,0m1,0a8.2,8.2 0 1,0 16.4,0a8.2,8.2 0 1,0 -16.4,0zm0.8,0a7.2,7.2 0 1,1 14.8,0a7.2,7.2 0 1,1 -14.8,0m1,0 a6.4,6.4 0 1,1 12.8,0a6.4,6.4 0 1,1 -12.8,0z" pathshape.circled = "M2.5,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0m0.8,0 a5,5 0 1,1 11.4,0a5,5 0 1,1 -11.4,0" pathshape.diamond = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10z" pathshape.diamondd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm1,0 l 6,8.5 l 6,-8.5 -6,-8.5 -6,8.5zm1,0 l 5,-7 5,7 -5,7 -5,-7z" pathshape.diamonddd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm0.75,0 l 6.25,9 l 6.25,-9 -6.25,-9 -6.25,9zm0.75,0 l 5.5,-8 5.5,8 -5.5,8 -5.5,-8zm0.75,0 l 4.75,7 l 4.75,-7 -4.75,-7 -4.75,7zm0.75,0 l 4,-6 4,6 -4,6 -4,-6z" pathshape.crossd = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0zM2.3,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.cross = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0z" pathshape.thincross = "M2,12 l6,0 l0,6 l4,0 l0,-6 l6,0 l0,-4 l-6,0 l0,-6 l-4,0 l0,6 l-6,0z" pathshape.fivepointstar = "M10 0 L12.245 6.91 19.511 6.91 13.633 11.18 15.878 18.09 10 13.82 4.122 18.09 6.367 11.18 0.489 6.91 7.755 6.91Z" pathshape.fivepointstard = "M10 1.5 L 11.90825 7.3735 18.08435 7.3735 13.08805 11.003 14.9963 16.8765 10 13.247 5.0037 16.8765 6.91195 11.003 1.91565 7.3735 8.09175 7.3735 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sixpointstar = "M10 0 L12.323 5.977 18.66 5 14.645 10 18.66 15 12.323 14.023 10 20 7.677 14.023 1.34 15 5.355 10 1.34 5 7.677 5.977Z" pathshape.sixpointstard = "M10 1.5 L 11.97455 6.58045 17.361 5.75 13.94825 10 17.361 14.25 11.97455 13.41955 10 18.5 8.02545 13.41955 2.639 14.25 6.05175 10 2.639 5.75 8.02545 6.58045 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sevenpointstar = "M10 0 L12.048 5.747 17.818 3.765 14.602 8.95 19.749 12.225 13.69 12.943 14.339 19.01 10 14.72 5.661 19.01 6.31 12.943 0.251 12.225 5.398 8.95 2.182 3.765 7.952 5.747Z" pathshape.sevenpointstard = "M10 1.5 L11.7408 6.38495 16.6453 4.70025 13.9117 9.1075 18.28665 11.89125 13.1365 12.50155 13.68815 17.6585 10 14.012 6.31185 17.6585 6.8635 12.50155 1.71335 11.89125 6.0883 9.1075 3.3547 4.70025 8.2592 6.38495 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.eightpointstar = "M10 0 L11.88 5.46 17.071 2.929 14.54 8.12 20 10 14.54 11.88 17.071 17.071 11.88 14.54 10 20 8.12 14.54 2.929 17.071 5.46 11.88 0 10 5.46 8.12 2.929 2.929 8.12 5.46Z" pathshape.eightpointstard = "M10 0 L10 1.5 L11.598 6.141 16.01035 3.98965 13.859 8.402 18.5 10 13.859 11.598 16.01035 16.01035 11.598 13.859 10 18.5 8.402 13.859 3.98965 16.01035 6.141 11.598 1.5 10 6.141 8.402 3.98965 3.98965 8.402 6.141ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.ring="M2.6,9.5a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.semicircle = "M0,5 C0,18.3,20,18.3,20,5 L 0,5z" pathshape.arrowhead = "M 10,10.2 l 10,2.8 -2,-3 2,-3 -10,2.8 -0.2,0.2z" pathshape.boxd=pathshape.squared pathshape.boxdd=pathshape.squaredd pathshape.ellipsed=pathshape.circled pathshape.ellipsedd=pathshape.circledd local msg={} local function debugmsg(txt) table.insert(msg,txt) end local pmsg={} local function previewMsg(txt) table.insert(pmsg,txt) end local function colorLookup(color) for c,l in pairs(colorList) do if string.find(color,c) then return l end end return color end local function getColor (color,chk) local c local opacity="100" if not color or color=='' then color='hardgrey' end if color=="transparent" then return color end if color=="background1" then color='background' end if string.byte(color,1,1)==35 and (#color == 7 or #color == 9) then c=color elseif string.byte(color,1,1)==35 and #color == 4 then c=string.sub(color,1,2)..'f'..string.sub(color,3,3)..'f'..string.sub(color,4,4)..'f' else local s=color..'1' s= s:sub(0,s:find("%d")-1) opacity=string.match(color,"%d+") if not CTB[s] then s= colorList[s] end -- check for synonyms and translations if not CTB[s] then debugmsg(mw.addWarning('color = '..color..'. The color name is not defined. Used default grey instead')) end c=CTB[s] or CTB.hardgrey end if opacity and (tonumber(opacity) < 100) and string.find(c,"#")==1 and string.len(c)==7 and opacity~="" then local hexval=string.format("%x",(math.floor(tonumber(opacity)*2.55))) c=c..hexval end return c end function p.colorvalue(frame) -- enable external access to the CTB colorTable values. usage: {{#invoke:OSM Location map|colorvalue|color=hard blue}} local c if not frame.args.color or frame.args.color=='' then c='grey' else c=string.lower(string.gsub(frame.args.color,'%s+','')) end return string.upper(string.sub(getColor(c),2)) end local function checkColors(color) local c=getColor(color,'check') local opacity =1 -- calculate colour brightness and return black or white for contrast if c=='transparent' then return c,'#000000',0 end if not (string.find(c,'#')==1) then return c,'#FFFFFF',0 end if #c>8 then opacity= tonumber('0x'..(string.sub(c,8,9)))/500 end local r=tonumber('0x'..(string.sub(c,2,3)))/255 local g=tonumber('0x'..(string.sub(c,4,5)))/255 local b=tonumber('0x'..(string.sub(c,6,7)))/255 if 0.2126 * r + 0.7152 * g + 0.0722 * b / opacity < 0.7 then return c,'#FFFFFF',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity else return c,'#000000',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity end end local function morethan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) and tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%-?%f[%d]%d[,.%d]*%f[%D]') ) b = tonumber(string.match(b, '%-?%f[%d]%d[,.%d]*%f[%D]') or '0') end return a>b end local function lessthan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) and tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%-?%f[%d]%d[,.%d]*%f[%D]')) b = tonumber(string.match(b, '%-?%f[%d]%d[,.%d]*%f[%D]') or '0') end return a<b end local function getsize(size) --size1 is between 1 and 3 values, each with px, equating to width,height,corner-rounding --eg '15px 25px 5px' (spaces are optional) or '18px'. returns three numbers local sizeval = {} for v in string.gmatch(size, "[^px]+") do table.insert(sizeval,v) end sizeval[1] = tonumber(sizeval[1]) or 13 sizeval[2] = tonumber(sizeval[2]) or sizeval[1] sizeval[3] = tonumber(sizeval[3]) or 0 return sizeval[1],sizeval[2],sizeval[3] end local function coord2text(coord) -- looks through the output from {{coord}} to find the lat and long decimal values -- and converts compass points to minus or not-minus, return with separating comma. local lat = string.match(coord,'[%.%d]+°[NS]') local lon = string.match(coord,'[%.%d]+°[EW]') local neg={N="",S="-",W="-",E=""} return neg[string.match(lat, '[NS]')]..string.match(lat,'[%.%d]+')..","..neg[string.match(lon, '[EW]')]..string.match(lon,'[%.%d]+') end local function convertCoordsTrad (row) local coords='' if row and string.find (row,'<span class="geo">') then local a,b=string.find(row,'<span class="geo">') local start=b+1 a,b=string.find(row,"</span>",b) local finish=a-1 coords=string.sub(row,start,finish) coords=string.gsub(coords,'; ',',') end return coords end local function convertCoords (row) local start,finish,lat,lon,coords,says if row then local a,b=string.find(row,"<span class=") start=a while a do -- find the final span> finish=b a,b=string.find(row,"span>",b) end if start then coords= string.sub(row,start,finish) says="" if string.find(coords,'<span class="error">') then error("coord error: badly formed coordinates",0) end coords=coord2text(coords) coords = string.sub(row,1,start-1)..coords..string.sub(row,finish+1) else coords=row end return coords else return "Nothing to see here" end end local function fillCommas(val,max) local line='' if not val then line=',' -- ensure there is some content else line = val --string.lower(string.gsub(val,"%s+","")) -- or strip spaces end if string.find(line,',') == 1 then line=' '..line end -- ensure initial comma is not skipped local _, count=string.gsub(line,",","") -- add enough subsequent commas for all entries line=line..string.rep(',',max-count) while(string.find(line,",,") ) do line=string.gsub(line,",,",", ,") --ensure string.gmatch doesn't ignore any empty items by padding with spaces end return line end local function makeLinkBox(left,top,wid,label, link) local linkBoxName='Transparent square.svg' if visibleLinks or '' =='yes' then linkBoxName='Red hollow square.svg' end local builder = mw.html.create('div') --display:inline-block; local t= string.find(label,'wikidata') -- exclude wikidata from tooltip line if t then label=string.sub(label,1,t-2) -- don't show wikidata value in tooltip if string.find(link,'wikidata') then link = '' end -- don't link on click to wikidata (if there was not another link) end builder :cssText('position:absolute;left:'..tostring(left-1-wid/2)..'px;top:'..tostring(top-1 + math.min(wid/2-12,0) - wid/2)..'px') :wikitext(string.format( '[[File:%s|%dpx|link=%s|%s]]', linkBoxName, wid+2, link, label )) return tostring(builder) end local function extractItem(row,searchItem) -- remove text following a searchItem or start of line, which might be in quote-marks to allow commas local xend,xstart=1,0 if not row then return '','' end if searchItem then xend,xstart= string.find(row or '',searchItem or 'image:') end if not xstart then return string.gsub(string.gsub(row or '',"%b\"\"", ''),"%b\'\'", '') or '','' end while row:byte(xstart+1) == 32 and xstart<#row do -- skip over any leading spaces xstart=xstart+1 end local xbyte=row:byte(xstart+1) if xbyte == 34 or xbyte == 39 then -- are they wrapped in single or double quotes xstart=xstart+1 xend=row:find(string.char(xbyte),xstart+1) else xend = row:find(',',xstart+1) -- if no quotes, we assume no commas if not xend then xend=#row+1 end end -- return residual row and extracted text return row:sub(0,xstart)..row:sub(xend), row:sub(xstart+1,xend-1) end local function itemCheck(item,ext) if not item then return nil end if not ext then ext='' end return (string.match(item,"[%.%-?%d]+") or '0')..ext end local function stripdivs(line) return string.gsub(line or '',"%b<>", ' ') end local function splitItem(item,max) -- takes a commas-sep list and returns a table of lowercase items with no spaces, or nil local r={} local x=1 item=string.lower(fillCommas(item,max)) for t in string.gmatch(item,"[^,]+") do r[x]=string.gsub(t,"%s+","") if r[x]=='' then r[x]= nil else -- residual items might have commas if x>max then r[max]=(r[max] or '')..', '..r[x] end end x=x+1 end return r end local function ParseShapeTypes (result,args,sgval) -- for use with compressed, comma-separated 'sg plus dots' parameters --[[ shape table items and default values as set at top of page shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items label text textSZ="11px", textCL="white", textNG="0deg", textAT=attributes ("bold" and/or "italic") --sgd items for dotTag tagSize="11px", tagColor="darkgrey", tagSpacer="0px", tagAngle="0deg", --sge extension line textEW="1px", textEC="darkblue", textES="solid" --sgf fx for text labels textSP="0px" textLH="100%" textOL="1px", textBG="paleground", <!--| sga = shape,Size,Color,Angle|sgb= outlineWidth,color,style eg: sga1=circle,14px,blue,0deg| sgb1=0px,dark grey,solid | sgc=textSize,color,angle,bold/italic | sgd=tagSize,Color,Spacer,Angle eg: sgc1=11px,dark grey,0deg,normal| sgd1=9px,white,0px,0deg | sge=lineWidth,color,style |sgf=textspacing,lineHeight%,outlinepx,backgroundcolor, [bold,italic] eg: sge1=0px,black, solid| sgf1=0px,120%,0px,background | dot=shape-group/lat/lon/title/dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> --]] if args["sgn"..sgval] then local sgname=string.match(args["sgn"..sgval],"(%w+)(.*)") sgNames[sgname]=sgval end local parent= args["sgp"..sgval] if parent then parent=string.match(parent,"(%w+)(.*)") local pos= string.find(parent,"%d+") if pos == 1 then parent=string.match(parent,"%d+") else parent=sgNames[parent] or '1' end end if sgval~='H' then if not parent or tonumber(parent) > tonumber(sgval) then if sgval=="1" then parent="0" else parent="1" end end end local itemTab, line, filename result[sgval]={} result[sgval].shapeType=sgval line,filename=extractItem(args['sga'..sgval] or '','image:') if sgval=='1' and not args.sga1 then line='circle,12px,blue,0deg' end -- ensure there is a parent=1 sga result[sgval].shapeFile=filename or '' -- sga= Attributes for shape itemTab=splitItem(line,4) result[sgval].shape = itemTab[1] or result[parent].shape result[sgval].shapeSize=itemTab[2] or result[parent].shapeSize result[sgval].shapeColor=itemTab[3] or result[parent].shapeColor result[sgval].shapeAngle=itemCheck(itemTab[4],'deg') or result[parent].shapeAngle -- sgb= Border outline attributes for shape itemTab=splitItem(args['sgb'..sgval],3) result[sgval].outlineWidth=itemCheck(itemTab[1],'px') or result[parent].outlineWidth result[sgval].outlineColor=itemTab[2] or result[parent].outlineColor result[sgval].outlineStyle=itemTab[3] or result[parent].outlineStyle --sgc=character attributes for label itemTab=splitItem(args['sgc'..sgval],4) result[sgval].textSZ=itemCheck(itemTab[1],'px') or result[parent].textSZ -- size of text in px result[sgval].textCL=itemTab[2] or result[parent].textCL -- colour for text result[sgval].textNG=itemCheck(itemTab[3],'deg') or result[parent].textNG -- Angle for text result[sgval].textAT=itemTab[4] or result[parent].textAT -- attributes bold, and/or italic --sgd=dotTag attributes itemTab=splitItem(args['sgd'..sgval],4) result[sgval].tagSize=itemCheck(itemTab[1],'px') or result[parent].tagSize result[sgval].tagColor=itemTab[2] or result[parent].tagColor result[sgval].tagSpacer=itemCheck(itemTab[3],'px') or result[parent].tagSpacer result[sgval].tagAngle=itemCheck(itemTab[4],'deg') or result[parent].tagAngle --sge= extension line attributes itemTab=splitItem(args['sge'..sgval],4) result[sgval].textEW=itemCheck(itemTab[1],'px') or result[parent].textEW -- width result[sgval].textEC=itemTab[2] or result[parent].textEC -- colour result[sgval].textES=itemTab[3] or result[parent].textES -- style --sgf= fx for label text itemTab=splitItem(args['sgf'..sgval],4) result[sgval].textSP=itemCheck(itemTab[1],'px') or result[parent].textSP -- spacing value for letters result[sgval].textLH=itemCheck(itemTab[2],'%') or result[parent].textLH -- Angle for text result[sgval].textOL=itemCheck(itemTab[3],'px') or result[parent].textOL -- width of text-border line result[sgval].textBG=itemTab[4] or result[parent].textBG -- color for text background return result end local function round(x,dec) -- x=number [, dec=integer] returns numeric value with upto dec decimals (all but first trailing zeros get truncated) if (dec or 0)==0 then return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5) --this avoids .0 where dec=0 end dec =10^(dec) return x>=0 and math.floor(x*dec+0.5)/dec or math.ceil(x*dec-0.5)/dec end local function maptogrid(t, r, test) t.width = tonumber(t.width) or 0 t.height = tonumber(t.height) or 0 t.lon = tonumber(t.lon) t.lat = tonumber(t.lat) t.lonbase = tonumber(t.lonbase) t.latbase = tonumber(t.latbase) t.zoom = tonumber(t.zoom) or 0 if not t.lon or not t.lat or not t.lonbase or not t.latbase then error( "maptogrid: missing values → " .. "lon=" .. tostring(t.lon) .. " lat=" .. tostring(t.lat) .. " lonbase=" .. tostring(t.lonbase) .. " latbase=" .. tostring(t.latbase) ) end local zoom = t.zoom local x = t.width / 2 + ( ( (math.rad(t.lon) * 6378137) - (math.rad(t.lonbase) * 6378137) ) / (156543.03 * math.cos(math.rad(t.latbase)) / (2 ^ zoom)) ) * (1 - (0.075 * (math.abs(t.latbase) / 90))) local y = t.height / 2 + ( ( (math.log(math.tan(math.rad(t.latbase) / 2 + math.pi / 4)) * 6378137) - (math.log(math.tan(math.rad(t.lat) / 2 + math.pi / 4)) * 6378137) ) / (156543.03 * math.cos(math.rad(t.latbase)) / (2 ^ zoom)) ) * (1 - (0.075 * (math.abs(t.latbase) / 90))) return round(x, r), round(y, r) end local function getScale(zoom, lat, magVal) zoom = tonumber(zoom) or 0 lat = tonumber(lat) if not lat then lat = 0 end if magVal then magVal = tonumber(magVal) if magVal and magVal > 1 and magVal <= 2 then zoom = zoom + (magVal - 1) end end local dist = (156543.03 * math.cos(math.rad(math.abs(lat))) / (2 ^ zoom)) / 17 local y if dist < 1 then y = round(dist * 10, 1) return tostring(y * 100) .. metres, tostring(round(y * 109, 0)) .. yards elseif dist < 18 then y = round(dist, 0) return tostring(y) .. km, tostring(round(y * 0.621371, 1)) .. miles elseif dist < 500 then y = round(dist / 10) return tostring(y * 10) .. km, tostring(round(y * 6.21371, 0)) .. miles else y = round(dist / 100) return tostring(y * 100) .. km, tostring(round(y * 62.1371, 0)) .. miles end end local function ParseData (args,dotval) -- for use with compressed, comma-separated 'sg plus dots' parameters -- takes a structured series of comma-separated items which get parsed as the following: -- dot(n)= (sgNumber or Name),{{coord}} or (lat,lon), (dotTag) -- dotlink(n) = single-parameter text to give wikilink and/or title used by tootlip, fullscreen dots and autocaption list -- dotlabel(n) = 'label text',pos(left,roght,top,bottom,centre,auto),(dx), (dy) pixel offsets, params, info -- dotpic(n) = single parameter wikimedia filename for an image to use in photopanel and/or fullscreen dots -- dotfeature(n)= 'mark-line' (,linewidth,style,gap) or 'photo-panel' (,image-dim,panel-width,panel-height), draws line to n-1 -- label is used if either a position and/or an x,y offset are not 0,0 ( if no label then dotTag will be put at at the x,y offset, or over the dot -- label text can be autoaligned if x,y puts it left or right of the dot, or centered if above/below) -- quote marks are not needed unless including commas within the label text -- param1 is optional items separated by spaces, and can include [nolabel nolist nomap hemisphere+1 hemisphere-1] -- info is free wikitext, to be used in the fullscreen dot box. (use dotpic to show a picture) --<!--| dotx=shape-group,[lat,lon or {{coord}} ], dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> local result={} local count=1 local row = convertCoords (args["dot"..dotval]) -- swap in any {{coord}} values so they are csv lat and lon row=fillCommas(row,4) result.code=dotval -- store the parameter name as the id code for item in string.gmatch(row,"[^,]+") do -- iterate through 'row', adding each csv item in turn, if present if count==1 then --see if it is a number or a name local pos= string.find(item,"%d+") if pos == 1 then result.group=string.lower(string.gsub(item,"%s+","")) else item=string.match(item,"(%w+)(.*)") -- ensure just a single word result.group=sgNames[item] end elseif count==2 then result.lat=tonumber(string.match(item,"[%.%-?%d]+")) or 0-- find the number, with no non-numeric stuff elseif count==3 then result.lon=tonumber(string.match(item,"[%.%-?%d]+")) or 0 elseif count==4 then result.dotTag=item:match( "^%s*(.-)%s*$" ) or "" -- dotTag allows for internal spaces, but no commas end count=count+1 end row, result.labelText= extractItem(args["dotlabel"..dotval]) result.labelText= string.gsub(result.labelText,"[%^]+","<br>") -- convert hats to line breaks local item=splitItem(row,6) result.labelPos=item[2] or 'center' result.dx=tonumber(string.match(item[3] or '0',"[%.%-?%d]+")) or 0 result.dy=tonumber(string.match(item[4] or '0',"[%.%-?%d]+")) or 0 result.param1=string.lower(item[5] or '') if string.find(result.param1,"hemisphere-1",1,true) then result.lon=result.lon-360 elseif string.find(result.param1,"hemisphere+1",1,true) then result.lon=result.lon+360 end local txt = '' if item[6] then -- ensure all info elements are included, including commas count=1 local max=6 for t1 in string.gmatch(fillCommas(row,max),"[^,]+") do if count>max then txt=txt..',' end if count>=max then txt=txt..t1 end count=count+1 end end result.info=txt result.imageName = args['dotpic'..dotval] -- Get first wikilinked item (if any) from the args.dotlink and set this plus the delinked text local testx=args["dotlink"..dotval] or '' result.dotLink=testx if testx ~= '' then testx=stripdivs(testx) result.title=delink({ testx }) local linkstart= string.find(testx,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then result.dlink=delink( { string.sub(testx,linkstart,string.find(testx,']]',1,true)+1),wikilinks='target' } ) else result.dlink='' end else result.dlink='' result.title='' end if args['dotfeature'..dotval] then local item=splitItem(args['dotfeature'..dotval],6) if (item[1] or '') =='photo-panel' then result.ppwidth= tonumber(string.match((item[3] or '110'),"%d+")) -- panel width result.ppheight= tonumber(string.match((item[4] or '48'),"%d+") ) --panel height result.photowidth=round(tonumber(string.match((item[2] or '1.3'),"[%.%-?%d]+")) * result.ppheight+1,0) -- dimension to set image size result.photoImage=result.imageName result.posType='photo-panel' elseif (item[1] or '') == 'mark-line' then local x=tonumber(dotval or '0') result.markDest=item[5] or tostring(x-1) result.mlWidth= tonumber(string.match((item[2]) or '',"%d+") or '1') result.mlStyle= item[3] or 'solid' result.mlGap=tonumber(string.match((item[4] or ''),"[%d]+") or '0') result.posType='mark-line' end --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end maplist.lon=result.lon maplist.lat=result.lat result.gridx, result.gridy = maptogrid(maplist,1) -- convert geo coords to grid xy - using values from maplist table return result end local function multiCheck (args, argName, argVal, defVal, alt) if not alt then alt='nonexistant' end if argVal=='H' and not args[argName..'H'] then argVal=highlightNum or '1' end if argVal=='' then return (args[argName] or args[alt] or (args[argName..'D']) or args[alt..'D'] or defVal) -- unnumbered args do not inherit from D or 1 else return (args[argName..argVal]) or (args[alt..argVal]) or (args[argName..'D']) or (args[alt..'D']) or (args[argName..'1']) or (args[alt..'1']) or defVal end end local function htmlTableLine (htmlTable,h1,h2,h3,h4,h5,type) if type == 'header' then if (h4 or '') ~= 'no-year' then h4='<th class="unsortable">'..h4 or ''..'</th>' else h4 = '' end if (h5 or '') ~= '' then h5='<th class="unsortable">'..h5..'</th>' else h5 = '' end return htmlTable..'<tr><th>'..(h1 or '')..'</th><th>'..(h2 or '')..'</th><th>'..(h3 or '')..'</th>'..h4..h5..'</tr>' else if (h4 or '') ~= 'no-year' then h4='<td>'..h4 or ''..'</td>' else h4 = '' end if (h5 or '') ~= '' then h5='<td>'..h5..'</td>' else h5 = '' end return htmlTable..'<tr><td>'..(h1 or '')..'</td><td>'..(h2 or '')..'</td><td>'..(h3 or '')..'</td>'..h4..h5..'</tr>' end end local function formatInt(number) if number == 0 then return notFound end local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)') int = int:reverse():gsub("(%d%d%d)", "%1,") return minus .. int:reverse():gsub("^,", "") .. fraction end local function convertToSqKm (value,punit) if string.find(punit, 'Q35852') then value = value / 100 -- convert to sq km if it's hectares elseif string.find(punit, 'Q232291') then value = value /(1/2.59) -- or this if it's square miles elseif string.find(punit, 'Q81292') then value = value / 247.1 -- or this if it's acres end return value end local function assignTradstyleShape(shapeResult,default,dotResult,args,nval) local item,itemTab local autoDotTag='' local shapeWidth,shapeHeight=0,0 local argval=nval -- to catch the unnumbered shape series local qvalue='' if argval=='0' then argval='' end if nval=='H' then shapeResult.H={} end -- Assign autovalues if a wikidata Qvalue has been supplied -- some preliminary documentation -- geo-data-type= default is P1082 (population) -- other options might include number of households (P1538) and per capita income (P10622) or land area (P2046) -- geo-data-list= this is a comma-separated list of Q values, for a manually generated list that doesn't require nymbered parameters -- geo-scalefactor = can enlarge or shrink all the dots in proportion, depending an what the map needs -- geo-region = needs to be a Qvalue of the larger administrative region that the settlements/sub-regions are in -- geo-sub-region needs to be a Pvalue, but will default to P150 (some pages use 'has parts' = P527) -- geo-data3= etc numbered Qvalue item, if used in conjunction with other shape/label parameters -- shapeD= sets default shape, which migh be n-circle to include numbers. -- geo-number-size= will give a fixed size for shape numbers. (defaults to 12 when using proportional dots) -- geo-boundaries= if set to 1 will add any Qvalues for the sub-regions to 'map-data-light'. -- show-geo-dots= to use proportional dots. Will be fixed size if 0. (set mark-sizeD=0 to have no dot at all, eg to show labels or boundaries) -- find-geo-dots=1 or 0 (needs to also include geo-region) -- label-posD = gets additional options: [off] gives no label, [on] will position lower centre, -- [on+] includes numeric values beside/below label, [on^] will always put it below. [on-] always puts values beside if args['geo-data'..argval] then -- and (args['mark-title'..argval] or '') ~= 'none' then local pvalue = args['geo-data-type'] or 'P1082' local plabel = mw.wikibase.getLabel(pvalue) if not args['geo-number-size'] then args['geo-number-size']=12 end if not args['label-posD'] then args['label-posD'] = 'on' end qvalue=args['geo-data'..argval] -- total poulation is a Qvalue wikidata page entry, or add up all the geo-data values or use a random 10000 local region = args['geo-region'] -- or mw.wikibase.getEntityIdForCurrentPage() local thisPop=0 local areatxt='' local yearVal='' if mw.wikibase.getBestStatements(qvalue,pvalue)[1] then thisPop=tonumber(mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.amount) or 0 if pvalue == 'P2046' then -- area, so convert from ha to sq km if needed (/100) thisPop = convertToSqKm(thisPop, mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.unit) end if mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers and mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers.P585 then yearVal = mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers.P585[1].datavalue.value.time if yearVal then yearVal = yearVal:sub(2,5) end -- wikidata reports an entire date and time, so extract the year end else debugmsg(mw.addWarning(mw.wikibase.getLabel(qvalue)..' ('..qvalue..') has no recorded '..plabel)) end local scalefactor=args['geo-scalefactor'] or 1 if argval=='1' and args['mark-size1'] and not args['mark-sizeD'] then args['mark-sizeD'] = args['mark-size1'] end if argval=='1' and args['label-pos1'] and not args['label-posD'] then args['label-posD'] = args['label-pos1'] end if not args['mark-size'..argval] then if args['show-geo-dots'] == '0' then args['mark-size'..argval] = args['mark-sizeD'] or '1' -- just use a 'point' for the label to attach to else if thisPop>0 then args['mark-size'..argval] = round(math.sqrt(thisPop/geoTotalPop*100) * 8 * scalefactor,0) else args['mark-size'..argval] = args['mark-sizeD'] or '1' args['shape-outline'..argval]=args['shape-outline'..argval] or args['shape-outlineH'] end end end local posTemp=string.lower(multiCheck(args,'label-pos',argval,'on')) args['label-pos'..argval] = posTemp posTemp=splitItem(posTemp,2)[1] if pvalue == 'P2046' then thisPop = round(thisPop,2) areatxt=sqkm..areatxt -- round decimals if sq km area elseif pvalue == 'P10622' then if mw.wikibase.getBestStatements(qvalue,pvalue)[1] then local punit=(mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.unit or ''):match("Q.*") -- extract Q value if punit == 'Q4917' then areatxt=' US$' elseif punit == 'Q4916' then areatxt=' euro' elseif punit == 'Q25224' then areatxt=' £GBP' else areatxt=mw.wikibase.getLabel(punit)..areatxt end end end if posTemp=='off' then else if not args['label'..argval] then args['label'..argval] = mw.wikibase.getLabel(qvalue) end local tline=posTemp:find("[%+%^%-]") if tline then local tval=posTemp:sub(tline,tline) local eline posTemp=posTemp:sub(1,tline-1) if posTemp == 'left' or posTemp == 'right' or string.find(posTemp,'east') or string.find(posTemp,'west') then if tval == '^' then eline = '^' else eline = ' ' end -- only add line break if 'forced', used for left/right elseif tval == '+' or tval == '^' then eline = '^' -- add linebreak unless 'forced' not to else eline = ' ' -- must be a dash, so no line break end args['label'..argval] = args['label'..argval]..eline..'('..formatInt(thisPop)..')' args['label-pos'..argval] = posTemp..args['label-pos'..argval]:sub(tline+1,#args['label-pos'..argval]) end end if posTemp=='on' then if argval=='1' and (not args.ldyD) then args.ldyD = args['ldy'..argval] or '0' end if not args['ldy'..argval] then args['ldy'..argval] = args.ldyD end -- ensure ldy for that dot is added to the position args['label-pos'..argval]='center' local _,br = string.gsub(args['label'..argval],'%^','') local item=splitItem(args['label-size'..argval],2) args['ldy'..argval] = (args['ldy'..argval] or 0) + ((item[1] or 11) * ((br+1)*0.6)) + ((args['geo-number-size'] or 12) / 2) end local nt='' if args['mark-title'..argval] == 'nolist' then nt='nolist,' end if not args['mark-title'..argval] or args['mark-title'..argval] == '' or args['mark-title'..argval] == 'nolist' then if mw.wikibase.getSitelink(qvalue) then args['mark-title'..argval] = nt..'[['..mw.wikibase.getSitelink(qvalue)..']]' else args['mark-title'..argval] = nt..mw.wikibase.getLabel(qvalue) end end -- If mark-title is not set locally then attempt Sitelink value, or use label if thisPop == 0 then areatxt='' end captionTableText[argval]={} captionTableText[argval][1]= args['mark-title'..argval] captionTableText[argval][2]= formatInt(thisPop) if maplist.width >= 280 then captionTableText[argval][3]= yearVal else captionTableText[argval][3]='no-year' end if args['show-q-values'] == '1' then captionTableText[argval][4]='<small>[[wikidata:'..qvalue..'|'..qvalue..']]</small>' else local nbsp = '&nbsp;' if maplist.width < 230 then nbsp = ' ' end captionTableText[argval][4]='' if captionTableText[argval][3] == 'no-year' then captionTableText[argval][2] = captionTableText[argval][2]..'<small><sup>'..nbsp..'[[wikidata:'..qvalue..'|WD]]</sup></small>' else captionTableText[argval][3] = captionTableText[argval][3]..'<small><sup>'..nbsp..'[[wikidata:'..qvalue..'|WD]]</sup></small>' end end if args['mark-title'..argval] == 'none' then -- no title else args['mark-title'..argval] = args['mark-title'..argval]..' ('..plabel..' '..formatInt(thisPop)..areatxt..')' end if not args['mark-description'..argval] then args['mark-description'..argval] = (mw.wikibase.getDescription(qvalue) or '')..'<br>' end if (not args['mark-image'..argval]) and (mw.wikibase.getBestStatements(qvalue,'P18')[1]) then args['mark-image'..argval] = mw.wikibase.getBestStatements(qvalue,'P18')[1].mainsnak.datavalue.value end item = multiCheck(args,'shape',argval,'circle') --default to circle if geo-data if not string.find(item,'svgpath,') then item=string.lower(item) end -- ensure svgpath items retain original case values else item = multiCheck(args,'shape',argval,'image') if not string.find(item,'svgpath,') then item=string.lower(item) end -- ensure svgpath items retain original case values end if string.find(item,'n-',0,true)==1 or string.find(item,'l-',0,true)==1 then autoDotTag=string.sub(item,0,1) item=string.sub(item,3) end if item == 'image' then shapeResult[nval].shape = 'image:' shapeResult[nval].shapeFile =multiCheck(args,'mark',argval,'Red pog.svg') shapeWidth=-1 else shapeResult[nval].shape = item end item= multiCheck(args,'mark-size',argval,(shapeResult[nval].shapeSize or '14px')) -- add geo-data as fallback if already assigned local a,b,c= getsize(string.gsub(string.gsub(item,',','px')..'px','pxpx','px')) if b==a and args['mark-dim'..argval] then b= b / tonumber(string.match(args['mark-dim'..argval],"[%.%-?%d]+")) end shapeHeight=b/2 item=tostring(a)..'px'..tostring(b)..'px'..tostring(c)..'px' shapeResult[nval].shapeSize= item itemTab=splitItem(multiCheck(args,'shape-color',argval,'hard red'),2) shapeResult[nval].shapeColor=itemTab[1] or 'hardred' item=itemCheck(itemTab[2],'%') -- jump through the various opacity hoops and add to color if needed if not item then item=itemCheck(args['shape-opacity'..argval],'%') end if item and item~='0%' and item~='100%' then shapeResult[nval].shapeColor=shapeResult[nval].shapeColor..item end shapeResult[nval].shapeAngle=itemCheck(multiCheck(args,'shape-angle',argval,'0'),'deg') or '0deg' if args['shape-angle'..argval] then shapeResult[nval].shapeAngleArg=true else shapeResult[nval].shapeAngleArg=false end --sort out the outline entry itemTab=splitItem(multiCheck(args,'shape-outline',argval,'transparent,0,100,solid'),4) shapeResult[nval].outlineColor=itemTab[1] or 'dark grey' shapeResult[nval].outlineWidth=itemCheck(itemTab[2],'px') or '1px' if itemTab[3] and itemTab[3]~='100' and itemTab[3]~='0' then shapeResult[nval].outlineColor=shapeResult[nval].outlineColor..itemCheck(itemTab[3],'%') end shapeResult[nval].outlineStyle=itemTab[4] or 'solid' -- label size, background, outline shapeResult[nval].textAT=args['label-attribute'..argval] or '' itemTab=splitItem( multiCheck(args,'label-size',argval,'12'),3) shapeResult[nval].textSZ=itemCheck(itemTab[1],'px') or '12px' if itemTab[2]=='outline' then shapeResult[nval].textBG=itemTab[3] or 'transparent' shapeResult[nval].textOL='1px' elseif itemTab[3]=='outline' then shapeResult[nval].textBG=itemTab[2] or 'transparent' shapeResult[nval].textOL='1px' else shapeResult[nval].textOL='0px' shapeResult[nval].textBG=itemTab[2] or 'transparent' end if getColor(shapeResult[nval].textBG)==CTB['hardgrey'] and shapeResult[nval].textBG~='hardgrey' then shapeResult[nval].textBG= 'transparent' end --label color etc itemTab=splitItem(multiCheck(args,'label-color',argval, 'darkgrey','label-colour'),2) shapeResult[nval].textCL=itemTab[1] or 'darkgrey' if itemTab[2] and itemTab[2]~='0%' and itemTab[2]~='100%' then shapeResult[nval].textCL=shapeResult[nval].textCL..itemTab[2] end shapeResult[nval].textSP=itemCheck( multiCheck(args,'label-spacing',argval,'0'),'px') -- sets letter-spacing in px shapeResult[nval].textLH=itemCheck( multiCheck(args,'label-height',argval,'120'),'%') -- sets line height, 120% default shapeResult[nval].textNG=itemCheck(multiCheck(args,'label-angle',argval,'0'),'deg') if args['label-angle'..argval] then shapeResult[nval].textNGarg=true else shapeResult[nval].textNGarg=false end --sgd=dotTag attributes shapeResult[nval].tagSize=(args['geo-number-size'] or tostring(shapeHeight*1.5))..'px' local c1,c2=checkColors(shapeResult[nval].shapeColor) shapeResult[nval].tagColor=c2 shapeResult[nval].tagSpacer='0px' shapeResult[nval].tagAngle='0deg' -- sge extension line attributes local shapePos=splitItem(multiCheck(args,'label-pos',argval,'right'),6) if shapePos[2]=='with-line' or shapePos[2]=='n-line' then shapeResult[nval].textEW=(shapePos[3] or '1')..'px' -- width shapeResult[nval].textEC=(shapePos[4] or shapeResult[nval].shapeColor or 'darkgrey') elseif shapePos[2]=='photo-panel' then shapeResult[nval].textEW='2px' -- width shapeResult[nval].textEC=shapeResult[nval].textCL else shapeResult[nval].textEW='0px' -- width shapeResult[nval].textEC='grey'-- colour end shapeResult[nval].textES='solid' if argval=='H' then return dotResult end --Assign dot values local dotItem={} dotItem.group=nval dotItem.code=nval dotItem.posType=shapePos[2] or 'nil' if (shapePos[2] or '') =='photo-panel' then dotItem.ppwidth= tonumber(string.match((shapePos[4] or '110'),"%d+")) dotItem.ppheight= tonumber(string.match((shapePos[5] or '48'),"%d+") ) dotItem.photowidth=round(tonumber(string.match((shapePos[3] or '1.3'),"[%.%-?%d]+")) * dotItem.ppheight+1,0) dotItem.photoImage=args['mark-image'..argval] --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end if (shapePos[2] or '') =='mark-line' then local x=tonumber(argval or '1') dotItem.markDest=shapePos[6] or tostring(x-1) dotItem.mlWidth= tonumber(string.match((shapePos[3] or '1'),"%d+")) dotItem.mlStyle= shapePos[4] or 'solid' dotItem.mlGap=tonumber(string.match((shapePos[5] or '0'),"[%d]+")) shapeResult[nval].textEC=shapeResult[nval].outlineColor or 'darkgrey' end if args['mark-coord'..argval] then itemTab=splitItem(convertCoordsTrad (args['mark-coord'..argval]),2) if itemTab[1] == nil or itemTab[2] == nil then debugmsg(mw.addWarning('Unable to read coordinates for mark-coord'..argval)) else dotItem.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) or 0 end else dotItem.lat=tonumber(string.match(args['mark-lat'..argval],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(args['mark-lon'..argval],"[%.%-?%d]+")) or 0 end if args['dateline'..argval] and (args['dateline'..argval]=='1' or args['dateline'..argval]=='-1') then dotItem.lon=dotItem.lon+(tonumber(args['dateline'..argval] ) *360) end maplist.lon=dotItem.lon maplist.lat=dotItem.lat dotItem.gridx, dotItem.gridy = maptogrid(maplist,1) local item=args['mark-title'..argval] or '' -- sort out the caption, wikilink and plaintext tooltip items from dotLink if item=='none' then dotItem.param1='nomap nolist' item='' end if string.find(item,'nolist') then dotItem.param1='nolist,' item = string.sub(args['mark-title'..argval],8) or args['label'..argval] end dotItem.dotLink=item if item ~= '' then item=stripdivs(item) dotItem.title=delink({item}) local linkstart= string.find(item,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then dotItem.dlink=delink({string.sub(item,linkstart,string.find(item,']]',1,true)+1),wikilinks='target'}) -- debugmsg('dlink for '..item..' is '..dotItem.dlink) else dotItem.dlink='' end else dotItem.dlink='' dotItem.title='' end if autoDotTag=='n' then item=nval elseif autoDotTag=='l' then item=string.char(64+tonumber(nval)) else item='' end dotItem.dotTag = args['numbered'..argval] or item if shapePos[2]=='n-line' and (args['label'..argval] or args['label'..argval]=='') then if dotItem.dlink =='' then item=dotItem.dotTag..' '..args['label'..argval] else item='[['..dotItem.dlink..'|'..dotItem.dotTag..']] '..args['label'..argval] end else item=(args['label'..argval] or '') end if args['labela'..argval] then item = item..'^'..args['labela'..argval] end if args['labelb'..argval] then item = item..'^'..args['labelb'..argval] end local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end dotItem.labelText = a -- convert hats to line breaks if argval=='' then item = (args['label-offset-x']) or (args.ldx) or '0' else item=args['label-offset-x'..argval] or args['ldx'..argval] or args['label-offset-xD'] or args.ldxD or args['label-offset-x1'] or args.ldx1 or '0' end dotItem.dx=tonumber(string.match(item,"[%.%-?%d]+")) if argval=='' then item = (args['label-offset-y']) or (args.ldy) or '0' else item=args['label-offset-y'..argval] or args['ldy'..argval] or args['label-offset-yD'] or args.ldyD or args['label-offset-y1'] or args.ldy1 or '0' end dotItem.dy=tonumber(string.match(item,"[%.%-?%d]+")) dotItem.labelPos=shapePos[1] if args['mark-image'..argval] then dotItem.imageName= args['mark-image'..argval] end dotItem.info=args['mark-description'..argval] or '' table.insert(dotResult,1,dotItem) -- add to start of list, so they are in reverese order for displaying -- debugmsg(dotItem.code..' = '..dotItem.lat..','..dotItem.lon..': '..dotItem.labelText..' - '..dotItem.title..', '..shapeResult[nval].shapeSize) return dotResult end local function tradstyleParseShapes(args,dotTable,dotmax) local sgNumbers,sgSortable={},{} for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['geo-data'..x] and (not args['mark-coord'..x]) and (not args['mark-lat'..x]) then -- get lat and lon values from wikidata and put them into the args.lat and args.lon for use wherever needed if (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1]) then args['mark-lat'..x] = (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1].mainsnak.datavalue.value.latitude) or '0' args['mark-lon'..x] = (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1].mainsnak.datavalue.value.longitude) or '0' local p=args['geo-data-type'] or 'P1082' if mw.wikibase.getBestStatements(args['geo-data'..x],p)[1] then local n = tonumber(mw.wikibase.getBestStatements(args['geo-data'..x],p)[1].mainsnak.datavalue.value.amount) or 0 if p == 'P2046' then -- area, so convert to sq km if needed (/100) n = convertToSqKm( n, mw.wikibase.getBestStatements(args['geo-data'..x],p)[1].mainsnak.datavalue.value.unit) end geoTotalPop = geoTotalPop + n end else debugmsg(mw.addWarning('could not find coords for '..mw.wikibase.getLabel(args['geo-data'..x])..', '..args['geo-data'..x])) end end if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) then sgNumbers[x]=x -- add it to the list end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form (needs a second table, seemingly) if args['mark-coord'] or (args['mark-lat'] and args['mark-lon']) then table.insert(sgSortable,'0') end -- add it to the end of the list local default={} default.D={} local dotResult={} for k,sgnum in pairs(sgSortable) do -- work through the sorted list, parsing each set of shapes in turn, from 1 upwards shapeList[sgnum]={} dotTable=assignTradstyleShape(shapeList,default,dotTable,args,sgnum) end dotTable=assignTradstyleShape(shapeList,default,dotTable,args,'H') -- construct an extra highlight shapeitem local unitConvert=1 local areaUnits='' if args['geo-region'] and (args['find-geo-dots'] or '1') ~= '0' then local region = args['geo-region'] -- or mw.wikibase.getEntityIdForCurrentPage() local pvalue = args['geo-data-type'] or 'P1082' local plabel = mw.wikibase.getLabel(pvalue) local subhead='' -- Assemble the subhead content, a line of text about the region as a whole if region and mw.wikibase.getBestStatements(region,pvalue)[1] then local punit=mw.wikibase.getBestStatements(region,pvalue)[1].mainsnak.datavalue.value.unit:match("Q.*") -- extract Q value if pvalue == 'P10622' then -- add other currency-based pvalues here areaUnits=mw.wikibase.getLabel(punit) end if punit == 'Q35852' then unitConvert=100 areaUnits = ' '..sqkm -- if its hectares, convert to sq km elseif punit == 'Q232291' then unitConvert = (1/2.59) areaUnits = ' '..sqkm -- or this if it's square miles elseif punit == 'Q81292' then unitConvert = 247.1 areaUnits = ' '..sqkm -- or this if it's acres elseif punit == 'Q712226' then unitConvert = 1 areaUnits = ' '..sqkm -- or this if it's already sq km elseif punit == 'Q4917' then areaUnits=' US$' -- replace long version with some shortform currencies () elseif punit == 'Q4916' then areaUnits=' euro' elseif punit == 'Q25224' then areaUnits=' £GBP' end -- if pvalue == 'P2046' then -- may need to add other instances of sq km so add in other P-items here if they need units showing -- end geoTotalPop=(mw.wikibase.getBestStatements(region,pvalue)[1].mainsnak.datavalue.value.amount) / unitConvert local yearVal if mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers then yearVal = mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P585[1].datavalue.value.time if yearVal and yearVal:sub(1,1) == '+' then yearVal = yearVal:sub(2,5) if mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459 and mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459[1] then local methodVal = mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459[1].datavalue.value.id if methodVal then yearVal=yearVal..' '..mw.wikibase.getLabel(methodVal) end end yearVal = ' ('..yearVal..')' else yearVal ='' end end subhead=totalItems..' '..mw.wikibase.getLabel(region)..' '..plabel..' ([[wikidata:'..region..']]) = '..formatInt(round(geoTotalPop),1)..areaUnits..yearVal else if pvalue == 'P2046' then areaUnits = sqkm end subhead=totalItems..' '..mw.wikibase.getLabel(region)..' '..plabel..' ('..sumOfDatapoints..') = '..formatInt(round(geoTotalPop),1)..areaUnits end if maplist.width < 350 and #plabel > 4 then plabel=string.sub((plabel or ' '),1,3)..'.' end if maplist.width < 330 then numberCol = ' ' end if maplist.width < 280 then year = 'no-year' end captionTable = subhead if (args['show-q-values'] or '') ~= '1' then captionTable = captionTable..'<div style="float:right;"><small> (WD&nbsp;=&nbsp;'..wikidataLink..')</small></div><br>' wikidataLink='' end captionTable = captionTable..'<table class="wikitable plainrowheaders sortable mw-collapsible mw-collapsed" style="width: 100%;"><caption>'..(args.toggletext or toggletext)..' </caption>' captionTable = htmlTableLine(captionTable,numberCol,mw.wikibase.getLabel(region),plabel..' '..areaUnits, year, wikidataLink,'header') end return dotTable end local function checkfortooltip (title,dx,dy,dotlabel,dlink,nolabel) -- returns tlink if available, and dlink, if needed and tshape=true if shape needed local tshape,tlink = false,"" if dlink~='' and not nolabel then tlink=dlink end if (tlink=="" or nolabel) and title~="" then tshape=true end -- tshape flags True if title is wanted for shape if (not (dx==0 or dy==0) or dotlabel=='') and title~='' then tshape=true end -- add tooltip to shape if its number has moved return tshape,tlink end local function tshift(angle) -- adjustment to place text near the centre of a triangle, shifted to allow rotation of triangle shape local x=tonumber(string.match(angle,"%-?%d+")) if x<0 then x=360+x end -- set to a single degree direction, 0 to 360 if x>359 then return 0,0 end -- shift the centre of the triangle based on rotation value if x <=40 or x>=320 then return -0.17,0 -- triangle up= -shiftv elseif x>=140 and x<=220 then return 0.17,0 --triangle down= +shiftv elseif x >220 then return 0,-0.17 --triangle left= -shifth elseif x >40 then return 0,0.22 --triangle right= +shifth end return 0,0 end local function makeTriangle(result,row,shape,outline,tlink) local w,h,r=getsize(shape.shapeSize) if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) w=w+p*2 h=h+p*2 end table.insert(result,'<div ') if tlink then table.insert(result,' title="'..row.title..'" ') end table.insert(result,'style="display:inline-block; position: absolute') if shape.shapeAngle ~= '0deg' then table.insert(result,'; transform: rotate('..shape.shapeAngle..')') end local shiftv,shifth=0,0 shiftv,shifth=tshift(shape.shapeAngle) table.insert(result,'; top: '..tostring(row.gridy-h/2+h*shiftv)..'px') table.insert(result,'; left: '..tostring(row.gridx-w/2+w*shifth)..'px; width: 0; height: 0; outline-width: 0px') table.insert(result,'; border-left: '..tostring(w/2)..'px solid transparent') table.insert(result,'; border-right: '..tostring(w/2)..'px solid transparent') if outline then -- fill with outline colour, to make a 'base layer' or shape colour table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.outlineColor).. '">') else table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.shapeColor).. '">') end table.insert(result,'</div>') end local function makeSquare(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) if shape.shape=='square' then h=w end -- squares are always square!. box can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end if r~=0 then div:css('border-radius',tostring(r).."px") end if shape.shape=='panel' then div:css('top', tostring(row.gridy).."px") div:css('left', tostring(row.gridx).."px") else div:css('top', tostring(row.gridy-h/2).."px") div:css('left', tostring(row.gridx-w/2).."px") end div:css('width', tostring(w).."px") div:css('height', tostring(h).."px") div:css('background-color', getColor(shape.shapeColor) ) div:css('color', 'inherit') table.insert(result,tostring(div)) end local function makeCircle(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding if shape.shape=='circle' then h=w end -- circles are always round. ellipse can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end div :css('top', tostring(row.gridy-h/2).."px") :css('left', tostring(row.gridx-w/2).."px") :css('width', tostring(w).."px") :css('height', tostring(h).."px") :css('border-radius', "50%") :css('background-color', getColor(shape.shapeColor) ) :css('color', 'inherit') table.insert(result,tostring(div)) end local function makeRuleA(result,row,shape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local lineV=0 if shape.shape=='rulea' then lineV=oWid*3+16 end table.insert(result,"<div style=\"display:inline-block; position: absolute") -- create a square transparent container, which will rotate line and arrow together table.insert(result,"; top:"..tostring(row.gridy - w/2).."px") table.insert(result,"; left:"..tostring(row.gridx - w/2).."px") table.insert(result,"; width:"..tostring(w).."px") table.insert(result,"; height:"..tostring(w).."px; background:transparent; color:inherit;") table.insert(result,"; transform: rotate( "..tostring(tonumber(string.match(shape.shapeAngle,"[%.%-?%d]+")) - 90).."deg);\">" ) table.insert(result,"<div style=\"display:inline-block; position: absolute") -- put the line (as a border-right) across the container table.insert(result,"; top:"..tostring(lineV).."px") table.insert(result,"; left:"..tostring((w - oWid )/2).."px; width: 0px") table.insert(result,"; height: "..tostring(w -lineV).."px") table.insert(result,"; border-right: "..shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) table.insert(result,"; background:transparent; color:inherit;\"></div>") if shape.shape=='rulea' then table.insert(result,"<div style=\"display:inline-block; position: absolute; top: 0px") --and add arrow head table.insert(result,"; left:"..tostring(w/2-( oWid/2)-oWid*0.55-2).."px; width: 0; height: 0; outline-width: 0px") table.insert(result,"; border-left: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-right: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-bottom: "..tostring(oWid*3+16).."px solid "..getColor(shape.outlineColor).."\"></div>") end table.insert(result,"</div>") end local function makeCurveA(result,row,shape) -- draw a curve with Arrow ----- local w,h=getsize(shape.shapeSize) -- = width,height local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local Angle=tonumber(string.match(shape.shapeAngle,"[%.%d]+")) table.insert(result,'<div style="position: absolute;') --set up out div, which will allow the whole to rotate table.insert(result,'top:'..tostring(row.gridy - (w + oWid*3+16)/2)..'px;') table.insert(result,'left:'..tostring(row.gridx - ( w + oWid*3+16)/2)..'px; ') table.insert(result,'width: '..tostring(w+oWid*3+16)..'px; ') table.insert(result,'height: '..tostring(w+oWid*3+16)..'px; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate( '..tostring(Angle-120)..'deg);">') else table.insert(result,'transform: rotate( '..tostring(Angle -62)..'deg);">') end table.insert(result,'<div style="position: absolute;') --set up div for the rounded corner of a rectangle table.insert(result,'border-left: '..shape.outlineWidth..' '..shape.outlineStyle..' '..getColor(shape.outlineColor)..';') if shape.shape=='curvea' then table.insert(result,'border-radius: 10000px 0 0 '..tostring(w)..'px; top:0px; left:'..tostring(w*0.25)..'px;') else table.insert(result,'border-radius: '..tostring(w)..'px 0 0 10000px;') table.insert(result,'top:'..tostring((oWid*3+16)/2+w*0.15)..'px; left:'..tostring(w*0.25)..'px;') end -- and add a triangular arrow head table.insert(result,'width: '..tostring(w)..'px; height: '..tostring(w)..'px;"></div><div style="position: absolute; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate(180deg); top:'..tostring(w-1)..'px; ') else table.insert(result,'transform: rotate(0deg); top: '..tostring(0-( ( oWid*3+16)/2)+1+( w*0.15) )..'px;') end -- reverse table.insert(result,'left:'..tostring(0-( oWid*0.6)-2+(w*0.25))..'px;') table.insert(result,'width: 0; height: 0; outline-width: 0px; border-left: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-right: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-bottom: '..tostring(oWid*3+16)..'px solid '..getColor(shape.outlineColor)..';"></div></div>') end local function makeLineTo (result,x1,y1,x2,y2,oWid, oStyle, oCol,double,shape) table.insert(result,"<div style=\"display:inline-block; position: absolute;") -- draw a line between x1,y1 and x2,y2, px-coords where 0,0 is centre of frame -- Maths calculations thanks to ES local tw if double <= 1 then tw = tonumber(string.match(oWid,"[%.%d]+"))/2 -- shift centre by half the line width else tw = tonumber(string.match(oWid,"[%.%d]+")) + (double/2) - 0.5 -- shift centre by a line width + half the gap end -- debugmsg(oWid..'+'..double..'/2='..tw) table.insert(result,"left: "..tostring(x1+( (x2-x1)/2) - (math.sqrt( ( x2-x1)^2 + (y2-y1)^2 )/2) ).."px;") table.insert(result,"top: "..tostring(y1+( ( y2-y1 )/2) - tw).."px;") table.insert(result,"width: "..tostring(math.sqrt( (x2-x1 )^2 + ( y2-y1 )^2) ).."px;") table.insert(result,"height: "..tostring(double).."px; background-color:transparent; color:inherit; ") table.insert(result,"outline-width: 0; border-bottom: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) if double>1 then table.insert(result,"border-top: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) end local lineAngle=math.atan(( y2-y1)/( x2-x1 ) )*180/math.pi -- if x1 == x2 then lineAngle=90 end table.insert(result,"transform: rotate("..lineAngle.."deg);\"></div>") -- debugmsg('text angle started as '..shape.textNG..' and lineAngle = '..lineAngle) if (lineAngle < -90) or (lineAngle >90) then lineAngle=lineAngle-180 end if shape and not shape.textNGarg then shape.textNG=tostring(round(lineAngle,0))..'deg' end -- Put in line angle for a default text angle as well -- if (y2 >= y1) and (x2 <= x1) then lineAngle=lineAngle-180 debugmsg('arrow angle corrected') end if x2 < x1 then lineAngle=lineAngle-180 end if shape and not shape.shapeAngleArg and shape.shape == 'arrowhead' then shape.shapeAngle=tostring(round(lineAngle,0))..'deg' end -- line angle for arrowhead -- debugmsg('text angle is now '..shape.textNG..'with arrow at '..lineAngle) end local function makeClipPath(result,row,shape,outline,tshape) --tshape is a flag to show if the tooltip (title=) is wanted -- return the text css div code to position and draw a shape occupying a specified clippath if string.find(shape.shape, 'svgpath,') and #shape.shape > 9 then pathshape.svgpath=string.sub(shape.shape,9) shape.shape='svgpath' end if not pathshape[shape.shape] then debugmsg(mw.addWarning('shape'..row.code..' = '..shape.shape..'. The shape name is not defined')) return end local w,h,r=getsize(shape.shapeSize) if shape.shape == 'arrowhead' then -- enlarge arrowheads, as they only go to the middle of the notional shape-size w=w*2.5 h=h*2.5 end if string.match(shape.shape,"circle") or string.match(shape.shape,"square") then h=w end -- use ellipse and box for stretched shapes local shifth,shiftv = 0,0 if string.match(shape.shape,"triangle") then shiftv,shifth =tshift(shape.shapeAngle) end if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) or 0 w=w+p*2 h=h+p*2 end table.insert(result,"<div ") if tshape then -- Add tooltip if needed table.insert(result," title=\""..row.title.."\" ") end table.insert(result,"style=\"display:inline-block; position: absolute; background-color:") if outline then table.insert(result,getColor(shape.outlineColor)) -- fill with outline colour, to make a 'base layer' else table.insert(result,getColor(shape.shapeColor)) end table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;"..pathshape[shape.shape].."&#39;) ") -- adds the required clippath data from the table of pathshape string literals table.insert(result,"; top:"..tostring(row.gridy - 10 + h*shiftv).."px") table.insert(result,"; left:"..tostring(row.gridx - 10 + w*shifth).."px") table.insert(result,"; width:20px") -- needs to be a path within a 20px20px box, and then rescales using size values to match other shape sizes table.insert(result,"; height:20px; transform:") if shape.shapeAngle ~= "0deg" then table.insert(result," rotate("..shape.shapeAngle..")") end table.insert(result," scale("..tostring(w/16)..", "..tostring(h/16)..")") table.insert(result,"\"></div>") end local function makeImage(result,row,shape) local w,h,r=getsize(shape.shapeSize) local image=shape.shapeFile if not image or image=='' then image='Red pog.svg' end local imagediv=mw.html.create ('div') imagediv:css('position', "absolute") if shape.shapeAngle ~= "0deg" then imagediv:css('transform',"rotate("..shape.shapeAngle..")") end imagediv :css('top', (row.gridy+2 + math.min(h/2-12,0) - h/2).."px") --File seems to adjust pos for small images :css('left', (row.gridx-1-w/2).."px") :css('background-color', "transparent" ) :css('color','inherit') :wikitext('[[file:'..image..'|'..tostring(w+2)..'px|alt='..(row.title or '')..'|link=]]') table.insert(result,tostring(imagediv)) end local function makePhotoPanel(result,row,shape) local h=row.ppheight table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;' ) table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') table.insert(result, 'width: '..tostring(row.ppwidth)..'px; height: '..tostring(h)..'px; border-radius: 2px; color:inherit;') table.insert(result, 'background-color: #E8E8D6; outline: 2px solid '..getColor(shape.textCL)..'; box-shadow: 2px 2px 4px #33203335;"></div>') if row.photoImage and row.photowidth >0 then table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;') if row.labelPos=='left' or string.find(row.labelPos,'west') then table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') row.dx=row.dx+row.photowidth/2 else table.insert(result, 'left: '..tostring(row.gridx+row.dx + (row.ppwidth-row.photowidth) - row.ppwidth/2)..'px;') row.dx=row.dx-row.photowidth/2 end table.insert(result, 'background-color:transparent; color:inherit; border-radius: 2px;">') table.insert(result,'[[File:'..row.photoImage..'|x'..tostring(h)..'px|File:'..row.photoImage..']]</div>') row.labelPos='center' end end local function makePanelText(result, row, shape) local w,h,r=getsize(shape.shapeSize) local ty=tonumber(string.match(shape.textSZ or '11',"%d+") ) table.insert(result,'<div style="position:absolute; line-height: 120%; font-size: '..shape.textSZ..'; color:'..getColor(shape.textCL)..';') if row.labelPos == 'left' or string.find(row.labelPos,'west') then table.insert(result,'top: '..tostring(row.dy+row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+3)..'px; text-align: left; width:'..tostring(w)..'px;') elseif row.labelPos == 'right' or string.find(row.labelPos,'east') then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+w-3)..'px; text-align: right; width: max-content; transform: translateX(-100%);') elseif row.labelPos == 'top' or row.labelPos== 'north' then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') elseif row.labelPos == 'bottom' or row.labelPos=='south' then local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*1.1 table.insert(result,'top: '..tostring(row.gridy+w-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') else -- center or centre local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*0.6 table.insert(result,'top: '..tostring(row.gridy+(h/2)-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') end if shape.textSP and shape.textSP ~='0px' then table.insert(result,"letter-spacing: "..shape.textSP..';') end if shape.textLH and shape.textLH ~='120%' then table.insert(result,"line-height: "..shape.textLH..';') end table.insert(result,"vertical-align: middle;\">"..row.labelText.."</div>") end local function makeTextItem(result, row, shape, align, tlink, textItem, dotItem) local w,h,r=getsize(shape.shapeSize) table.insert(result,"<div ") if row.title ~= "" then table.insert(result," title=\""..row.title.."\" ") end local ty,bry,linkoffset = 0,0,0 local compy=0 local lh=tonumber(string.match(shape.textLH or '120',"%d+"))/100 if dotItem==1 or (dotItem==2 and row.posType~='n-line') then -- if there is a dotTag in the middle of the shape then use the tag settings ty=tonumber(string.match(shape.tagSize,"%d+")) or 0 table.insert(result,"style=\"position:absolute; line-height: 120%; top: "..tostring(row.gridy-ty*0.62)) table.insert(result,"px; left: "..tostring(row.gridx).."px; width: fit-content; ") table.insert(result,"text-align: center; color: "..getColor(shape.tagColor).."; background-color: transparent;") local trf="" if shape.tagAngle ~="0deg" then trf=" rotate("..shape.tagAngle..")" end table.insert(result, "transform: translateX(-50%)"..trf.."; font-size: "..shape.tagSize..";") if shape.tagSpacer~='0px' then table.insert(result, "letter-spacing:"..shape.tagSpacer..";") end else -- or add tfx settings for left, right or center align, colors, backgrounds, border-outline table.insert(result,'style="position:absolute; ') if dotItem==2 then -- dotTag is out at x,y so 85% ty=tonumber(string.match(shape.tagSize or '0',"%d+")) table.insert(result,'font-size: '..shape.tagSize..'; padding:0px 2px;line-height: 85%; top: '..tostring(row.dy+row.gridy-ty*0.52)) else -- it is labelText, so use textLH or 120% ty=tonumber(string.match(shape.textSZ or '11',"%d+")) if row.labelPos=='northwest' or row.labelPos=='northeast' then compy=-ty elseif row.labelPos=='southeast' or row.labelPos=='southwest' then compy=ty/2 end if row.labelPos and not (row.labelPos== 'auto' or row.labelPos=='') then bry=(select(2, string.gsub(row.labelText,"<br>", ""))*lh) -- is it a multiline text? expand by line-height /120%? if row.posType == 'with-line' then bry=0 end if row.labelPos=='bottom' or row.labelPos == 'south' then bry = 0 -- and shift by none, all or half elseif row.labelPos=='top' or row.labelPos == 'north' then if shape.shape=='image:' then bry= 1 + math.min(w/2-10,0)-bry*ty else bry= -bry*ty+2 end else bry=-bry*(ty/2 * lh) end end if row.posType == 'photo-panel' then bry=bry+3 end table.insert(result,'font-size: '..shape.textSZ..'; padding:0px 3px;line-height: '..shape.textLH..'; ') table.insert(result,'top: '..tostring(row.dy+row.gridy+compy+bry-ty*lh/2)) end table.insert(result,"px; left: "..tostring(row.dx+row.gridx).."px; color: "..getColor(shape.textCL).."; ") table.insert(result,"width: max-content; ") local trf="" if shape.textNG ~="0deg" then trf="rotate("..shape.textNG..")" end if shape.textOL~="0px" then table.insert(result,"background-color: "..getColor(shape.textBG).."; ") table.insert(result,"border: "..shape.textOL.." solid "..getColor(shape.textCL).."; border-radius:6px;") else table.insert(result,"background-color: transparent;") end if row.labelPos=="right" or string.find(row.labelPos,'east') then table.insert(result,"text-align: left; ") linkoffset=w if shape.textNG ~="0px" then table.insert(result,"transform-origin: left; transform: rotate("..shape.textNG.."); ") end elseif row.labelPos=="left" or string.find(row.labelPos,'west') then if shape.textNG ~="0px" then table.insert(result,"transform-origin: right;") end linkoffset=-w table.insert(result,"text-align: right; transform: translateX(-100%) "..trf.."; ") else table.insert(result,"text-align: center; transform: translateX(-50%) "..trf.."; ") end table.insert(result,'font-weight: normal; line-height: '..shape.textLH..'; letter-spacing:'..shape.textSP..'; vertical-align: bottom;') end if string.find(shape.textAT or '','bold') then textItem='<b>'..textItem..'</b>' end if string.find(shape.textAT or '','italic') then textItem='<i>'..textItem..'</i>' end if shape.textOL=='0px' and shape.textBG~='transparent' and dotItem==0 then table.insert(result,'\"><span style=\"background-color: '..getColor(shape.textBG)..'; color:inherit;\">'..textItem..'</span></div>') else table.insert(result,"\">"..textItem.."</div>") end if tlink~='' and not string.match(row.param1 or "","nolink") then table.insert(result,makeLinkBox(row.gridx+row.dx+linkoffset, row.gridy+row.dy+bry, 16, row.dotTag..' '..row.title,tlink)) end end local function getshapetable(row,shape) -- Construct CSS divs for a dot from shape and map data local result={} local w,h,rndg=getsize(shape.shapeSize) local tshape,tlink=checkfortooltip(row.title,row.dx,row.dy,row.dotTag,row.dlink,string.match(row.param1 or "","nolink") ) local align=row.labelPos or '' local offsetx,offsety=0,0 local ty=tonumber(string.match((shape.tagSize or 9),"%d+" )) if row.labelText and row.labelText~='' then ty=tonumber(string.match((shape.textSZ or 11),"%d+" )) else align='center' end -- it is just for dotTag, so justify center -- identify align value and extend offsets local widthzone,heightzone = (w/2)+1,(h/2)+1 local theta,r = math.deg( math.atan2(row.dy, row.dx)) , math.sqrt(row.dx^2 + row.dy^2) if align=='auto' or align=='' then if (theta < -112 or theta > 112) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "left" offsetx=1 elseif (theta > -68 and theta < 68) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "right" offsetx=-1 elseif theta <0 then offsety=ty/2-1 -- bottom else offsety=0-ty/2+1 -- top end elseif align=='left' or string.find(align,'west') then row.dx=row.dx - w/2 widthzone=4 offsetx=-1 elseif align=='right' or string.find(align,'east') then row.dx=row.dx + w/2 widthzone=4 offsetx=1 elseif align=='top' or align=='north' then if string.find(shape.shape,'curve') then row.dy=row.dy-(h/2)-12 heightzone=4 offsety=ty/2-1 else row.dy=row.dy - h-2 heightzone=4 offsety=ty/2-1 end elseif align=='bottom' or align=='south' then if string.find(shape.shape,'curve') then row.dy=row.dy+(h/2) heightzone=4 offsety=ty/2-1 else row.dy=row.dy + h+2 heightzone=4 offsety=0-ty/2-1 end end if shape.textEW ~= "0px" and not string.match(row.param1 or "","noline") then -- debugmsg('r-(w/2) > widthzone r-w/2='..tostring(r-(w/2))..', widthzone='..widthzone..', label='..(row.labelText or '')..', postype='..(row.posType or '')) makeLineTo(result, row.gridx+1, row.gridy-1, row.gridx+row.dx+offsetx, row.gridy+row.dy+offsety, shape.textEW, shape.textES,shape.textEC,1) -- debugmsg('n-line for '..row.code..':'..tostring(row.gridx+1)..', '..tostring(row.gridy-1)..', '..tostring(row.gridx+row.dx+offsetx)..', '..tostring(row.gridy+row.dy+offsety)..', with width '..tostring(shape.textEW)..'px '..(shape.textES or 'no style')..' and color '..shape.textEC) end if row.posType and row.posType=='mark-line' then if row.gridx2 and row.gridy2 then makeLineTo(result, row.gridx, row.gridy, row.gridx2, row.gridy2, tostring(row.mlWidth)..'px', row.mlStyle, getColor(shape.textEC),row.mlGap,shape) -- debugmsg('mark-line line drawn from '..row.code..' with width '..tostring(row.mlWidth)..'px '..(row.mlStyle or 'no style')..' and color '..getColor(shape.textEC)) end end if w ~= 0 then if shape.shape=='itriangle' then shape.shape='triangle' shape.shapeSize=tostring(w)..'px'..tostring(w/2)..'px' end if shape.shape=="triangle" then if shape.outlineWidth ~= "0px" then makeTriangle(result,row,shape,true,false) -- larger triangle to give the outline, if required end makeTriangle(result,row,shape,false,tshape) -- smaller triangle to fit over the top elseif shape.shape=="square" or shape.shape=="box" or shape.shape=='panel' then makeSquare(result,row,shape,tshape) elseif shape.shape=="circle" or shape.shape=="ellipse" then makeCircle(result,row,shape,tshape) elseif string.find(shape.shape,'image:')==1 then makeImage(result,row,shape) elseif shape.shape=="rulea" or shape.shape=='rule' then makeRuleA(result,row,shape) elseif shape.shape=="curvea" or shape.shape=="curvec" then makeCurveA(result,row,shape) else -- use a pathshape clipPath if shape.outlineWidth ~= "0px" then makeClipPath(result,row,shape,true,false) -- larger path-shape to give the outline, if required end makeClipPath(result,row,shape,false,tshape) end end if row.ppwidth and row.ppwidth>0 then makePhotoPanel(result,row,shape) end if shape.shape=='panel' and row.labelText then makePanelText(result, row, shape) else if row.dotTag and row.dotTag ~= "" then -- there is a dotTag if (row.dx==0 and row.dy==0) and w>0 then -- it is on the dot so if dotsize is not 0 any label is ignored makeTextItem(result, row, shape, align, tlink, '<b>'..row.dotTag..'</b>', 1) else if row.labelText and row.labelText~='' then -- tag and label both used makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.labelText, 0) else makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.dotTag, 2) -- tag is ouside the dot end end else if (row.labelText and row.labelText~='') then -- just the label. No tag makeTextItem(result, row, shape, align, tlink, row.labelText, 0) end end end if tlink and tlink~='' then table.insert(result,makeLinkBox(row.gridx, row.gridy,w+3,row.dotTag..' '..row.title,tlink)) end return table.concat(result) end local function getmapframecontent(args,use) local result, comma= {}, '' local propertyTable= {} local color=splitItem(string.lower( args['map-data-color'] or 'dark orange'), 2) if use == 'basemap' then table.insert(result, '[') propertyTable.stroke = "#000000" propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') if color[2] then propertyTable['stroke-opacity'] = tonumber(color[2] or '20')/100 end else propertyTable.title = args['map-data-text'] or args['map-data'] or '' propertyTable.stroke = getColor(color[1]) propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') propertyTable['stroke-opacity'] = tonumber(color[2] or '100')/100 end if args['map-data-inverse'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geomask', ids = args['map-data-inverse'], properties = {title = (args['map-data-text'] or ''), stroke = '#555555', fill = '#555555', ['fill-opacity'] = 0.1, ['stroke-width'] = 1, ['stroke-opacity'] = 0.5 } } table.insert(result, mapJson) comma=', ' end if args['map-data-heavy'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-heavy'], properties = { title = args['map-data-heavy'], stroke = propertyTable.stroke, ['stroke-width'] = 9, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.2 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data-light'] and args['map-data-light'] ~= '' then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-light'], properties = { title = args['map-data-light'], stroke = propertyTable.stroke, ['stroke-width'] = 3, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.4 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data'] then propertyTable['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.2 local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data'], properties = propertyTable } table.insert(result, comma .. mapJson) comma=', ' end if args['map-wdqs'] then -- inserts a SPARQL wdqs query to find geopoints, geoshapes etc from wikidata local mapSparql = mw.text.jsonEncode{ type = 'ExternalData', service = args['map-wdqs-type'] or 'geopoint', query = args['map-wdqs'], } table.insert(result, comma .. mapSparql) -- debugmsg('mapSparql = ' .. mapSparql) comma=', ' end if args['map-raw'] then table.insert(result, comma .. args['map-raw']) end if use=='basemap' then table.insert(result,']') end return table.concat(result) end --eg | minilocator=filename,bottom right,132px153px, 38%,60%, 22px local function makeLocatorMap (args, result) local miniFile,pos,itemlist,miniW,miniH, miniX,miniY,miniBox, miniBH if args['mini-locator'] then pos,miniFile=extractItem(args['mini-locator']) -- first item is filename, in quotes if it includes commas itemlist=splitItem(pos,5) -- put items in a table filename removed, position,WpxHpx, x%y%,box pos=itemlist[2] or 'right' miniW,miniH=getsize(itemlist[3]) miniX=tonumber(string.match(itemlist[4] or '0','[%d]+'))*miniW/100 miniY=tonumber(string.match(itemlist[5] or '0','[%d]+'))*miniH/100 miniBox=tonumber(string.match(itemlist[6] or '0','[%d]+')) miniBox=miniBox*miniW/100 miniBH=miniBox * maplist.height/maplist.width elseif args['mini-file'] then miniFile = args['mini-file'] pos=string.lower(args.minimap or 'right') if pos=='file' then pos='right' end miniW,miniH = tonumber(args['mini-width'] or 60), tonumber(args['mini-height'] or 60) -- find top left corner of locator miniBox,miniBH=tonumber(args['minimap-boxwidth'] or '0'),0 -- firm up pos offsets for dot (with % or not) and boxsize if any, miniX, miniY=args['minipog-gx'],args['minipog-gy'] if not miniX then miniX=tonumber(args['minipog-x'] or '0') else miniX=tonumber(miniX)*tonumber(miniW)/100 end if not miniY then miniY=tonumber(args['minipog-y'] or '0') else miniY=tonumber(miniY)*tonumber(miniH)/100 end if args['minipog-gx'] then miniBox=miniBox*miniW/100 end miniBH=miniBox * maplist.height/maplist.width else return end local miniTop,miniLeft=0,1 if not string.find(pos,'top') then --only use top left, as link box is in top right or put bottom left or right miniTop=maplist.height+2-miniH if string.find(pos,'right') then miniTop=miniTop-15 -- to avoid (c) line miniLeft=maplist.width-1-miniW end end table.insert(result,'<div style="position: absolute; outline-width: 1px; outline-style: solid; outline-color: white;') table.insert(result,'top: '..tostring(miniTop)..'px; left:'..tostring(miniLeft)..'px; width:'..tostring(miniW)..'px;' ) table.insert(result,'background-color:transparent; color:inherit;">[[File:'..miniFile..'|'..tostring(miniW)..'px|File:'..miniFile..']]</div>') if miniX and miniX>0 then if args['minipog-y'] then miniY=miniY/1.04 end if args['minipog-x'] then miniX=miniX/1.04 end if miniBox<1 then table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-3-6)..'px; left:'..tostring(miniX+miniLeft-3)..'px;') table.insert(result, 'width: 6px; background-color:transparent; color:inherit;">[[File:Red pog.svg|6px|link=]]</div>') else table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-miniBH/2)..'px; left:'..tostring(miniX+miniLeft-miniBox/2)..'px;') table.insert(result, 'width: '..tostring(miniBox)..'px; height:'..tostring(miniBH)..'px; outline:1px solid #AA1205; background-color:#AA120522; color:inherit;"></div>') end end end local function makeArcText(args,result,nval) local items, itemlist='',{} local arcText='' if args['arc'..nval] then items=convertCoords (args['arc'..nval]) items,arcText=extractItem(items) -- first item is text, in quotes if it includes commas itemlist=splitItem(items,9) -- put items in a table: text, lat,lon,size,color,angle,gap,radius,ellipse if itemlist[4]=='' then itemlist[4]='12' end itemlist[4]=string.match((itemlist[4] or '12'),"[%.%-?%d]+") if itemlist[5]=='' then itemlist[5]='grey' end itemlist[6]=string.match((itemlist[6] or '0'),"[%.%-?%d]+") itemlist[7]=string.match((itemlist[7] or '0'),"[%.%-?%d]+") itemlist[8]=string.match((itemlist[8] or '0'),"[%.%-?%d]+") end if args['arc-coord'..nval] then local itemTab=splitItem(convertCoordsTrad (args['arc-coord'..nval]),2) maplist.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) maplist.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) else maplist.lat=tonumber(string.match(args['arc-lat'..nval] or itemlist[2] or '0',"[%.%-?%d]+")) maplist.lon=tonumber(string.match(args['arc-lon'..nval] or itemlist[3] or '0',"[%.%-?%d]+")) end local arcX,arcY=maptogrid(maplist,6) arcText = args['arc-text'..nval] or arcText local fontSize =tonumber(args['arc-text-size'..nval] or itemlist[4] or '12') local textColor=getColor(string.gsub(args['arc-text-color'..nval] or itemlist[5] or 'grey','[%s]+','') ) local arcAngle= tonumber((args['arc-angle'..nval]) or (itemlist[6]) or '45')-90 local arcRadius =tonumber(args['arc-radius'..nval] or itemlist[8] or '0.05') local arcGap = tonumber(args['arc-gap'..nval] or itemlist[7] or '1')* ( ( math.sin(8-math.rad(arcRadius))^8 )+0.4 )*( ( fontSize+6 )/15 ) arcRadius=450*arcRadius*0.75 local ellipseFactor=tonumber(args['ellipse-factor'..nval] or itemlist[9] or '1') local arcRotate =arcAngle+90 if arcGap<0 then arcRotate=arcAngle-90 end local latF=arcY - fontSize + (0-(math.sin(math.rad(arcAngle))) * arcRadius) local lonF=arcX - fontSize +(0-(math.cos(math.rad(arcAngle))) * arcRadius) local step=1 for codepoint in mw.ustring.gcodepoint( arcText ) do -- block step=1,#arcText do table.insert(result,'<div style="position: absolute;') local posY=tostring(round( (latF + (math.sin(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius)) *ellipseFactor,2))..'px;' local posX=tostring(round( (lonF + (math.cos(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius))/ellipseFactor,2))..'px;' table.insert(result,' top: '..posY..' left: '..posX..' transform: rotate( '..tostring(round(arcRotate +((step-1)*arcGap)),2)..'deg);') table.insert(result,'width:'..tostring(fontSize*2)..'px; text-align: center; background-color:transparent; color: '..textColor..';') table.insert(result,'vertical-align: baseline; font-size: '..tostring(fontSize)..'px;">'..mw.ustring.char(codepoint)..'</div>') step=step+1 end end local function makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) local item={} itemdescription=stripdivs(itemdescription or '') local templon=lon if lon > 180 then templon=lon-360 end --for hemisphere+ or -1 dots if lon < -180 then templon=lon+360 end -- use 'real' coordinates for geohack label, while retaining shifted coords for plot if itemcolor=='transparent' then itemcolor='white' end itemcolor=getColor(itemcolor) -- ensure no opacity, which breaks maplink if string.find(itemcolor,'#')==1 and #itemcolor>7 then itemcolor=string.sub(itemcolor,1,7) end table.insert(item, '{ "type": "Feature", "properties": {') table.insert(item, ' "title": "'..itemtitle..'",') table.insert(item, ' "description": "'..itemdescription) table.insert(item, ' ([https://geohack.toolforge.org/geohack.php?params='..tostring(lat)..';'..tostring(templon)) table.insert(item, '_dim:2000 '..tostring(lat)..','..tostring(templon)..'])",') table.insert(item, ' "marker-symbol": "-number-'..string.gsub(group,'%W','')..'", "marker-size": "medium", "marker-color": "'..itemcolor..'" },') table.insert(item, ' "geometry": {"type": "Point", "coordinates": ['..tostring(lon)..','..tostring(lat)..'] } }') return table.concat(item) end local function makeLegendBox(result,args) local legend ={} local line,count, maxWidth='',1,8 local item line,item=extractItem(args.legendBox or '') local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end legend.Text = a -- convert hats to line breaks line=splitItem(line,6) -- (text, size, poition,background color, text/outline color, param options) legend.Size=line[2] or '150px80px1px' legend.Pos=line[3] or '10px10px' legend.Background=line[4] or 'beigeground' legend.Color=line[5] or 'darkbrown' legend.Param= line[6] or '' local argnum,legendCount,titleHeight='1',1,0 if (legend.Text and legend.Text~='') then titleHeight=15+(13.4*(select(2, string.gsub(legend.Text,"<br>", "")))) end local legendLine,legendGroup,legendY={},{},{} while args['legendItem'..argnum] do -- assign legendLine, legendGroup, legendY for each dot line,legendLine[legendCount] = extractItem(args['legendItem'..argnum] or '') line=splitItem(line,3) legendGroup[legendCount]=line[2] or argnum if shapeList[legendGroup[legendCount]] then maxWidth=math.max(tonumber(string.match(shapeList[legendGroup[legendCount]].shapeSize or '10','[%d]+')),maxWidth) else maxWidth=math.max(tonumber(string.match(shapeList['1'].shapeSize or '10','[%d]+')),maxWidth) end maxWidth=maxWidth+1 if line[3] then legendY[legendCount]=tonumber(string.match(line[3],'[%d]+')) else legendY[legendCount] = 3+maxWidth*(legendCount-1)+titleHeight -- if (legend.Text and legend.Text~='') then legendY[legendCount]=legendY[legendCount]+15 end end legendCount=legendCount+1 argnum=tostring(legendCount) end local w,h,r=getsize(legend.Size or '') local x,y=getsize(legend.Pos or '') local div=mw.html.create ('div') div:css('position', 'absolute') div:css('outline', '1px solid'..getColor(legend.Color)) if r~=0 then div:css('border-radius',tostring(r).."px") end div :css('top', y.."px") :css('left', x.."px") :css('width', w.."px") :css('height', h.."px") :css('line-height','105%') :css('background-color', getColor(legend.Background) ) :css('color','inherit') if not string.find(legend.Param,'noshadow') then div:css('box-shadow', '2px 2px 4px #33203335') end div:tag( 'div' ) :css('position', 'absolute') :css('top','1px') :css('left', (w/2).."px") :css('width',(w-8)..'px') :css('text-align', 'center') :css('color', getColor(legend.Color)) :css('transform', 'translateX(-50%)') :css('font-size','11px') :wikitext(legend.Text) :done() for lct=1,legendCount-1 do --local t=legendGroup[lct] local shape=shapeList[legendGroup[lct]] or shapeList['1'] local row={} row.gridx=3+maxWidth/2 row.gridy=(legendY[lct] or 0) + 5 if shape.shape=='image:' then row.gridy=row.gridy+6 end row.dx=0 row.dy=0 local legendShape= getshapetable(row,shape) div:wikitext(legendShape) :tag( 'div' ) :css('position', 'absolute') :css('top',(legendY[lct] or 0)..'px') :css('left', (maxWidth+6)..'px') :css('width', (w-maxWidth-6)..'px') :css('text-align', 'left') :css('line-height','103%') :css('color', getColor(legend.Color)) :css('font-size','10px') :wikitext(legendLine[lct]) :done() end div:allDone() table.insert(result,tostring(div)) end local function getQset (qValue,pValue,argNumber,args) local result={} local sortType=string.lower(args['geo-list-sort'] or 'a') local testValue='0' if sortType == 'alpha' or sortType == 'a' or sortType == 'ah' or sortType == 'al' then testValue = mw.wikibase.getLabel(qValue) elseif sortType == 'value' or sortType == 'v' or sortType == 'vh' or sortType == 'vl'then if qValue==nil or pValue == nil then debugmsg(mw.addWarning((qValue or 'nil')..' Qnumber for '..(pValue or 'nil'))) else if mw.wikibase.getBestStatements(qValue,pValue)[1] then testValue = mw.wikibase.getBestStatements(qValue,pValue)[1].mainsnak.datavalue.value.amount or '0' if pValue == 'P2046' then testValue = convertToSqKm(testValue, mw.wikibase.getBestStatements(qValue,pValue)[1].mainsnak.datavalue.value.unit) end end end elseif sortType == 'north' or sortType == 'n' or sortType == 'nh' or sortType == 'nl' then if mw.wikibase.getBestStatements(qValue,'P625')[1] then testValue = mw.wikibase.getBestStatements(qValue,'P625')[1].mainsnak.datavalue.value.latitude or 0 end elseif sortType == 'west' or sortType == 'w' or sortType == 'wh' or sortType == 'wl' then if mw.wikibase.getBestStatements(qValue,'P625')[1] then testValue = mw.wikibase.getBestStatements(qValue,'P625')[1].mainsnak.datavalue.value.longitude or 0 end end result[1]=testValue result[2]=argNumber return result end function p._main ( args ) local result={} local frame=mw.getCurrentFrame() local dotTable={} local magVal,scaleVal = args.magnify,'' local origH,origW=maplist.height,maplist.width if magVal then --set up the values needed to magnify the top-right portion of the map magVal=tonumber(string.match(magVal or '0',"[%.%-?%d]+")) or 1 if magVal>1 and magVal <=2 then maplist.height= round(maplist.height/magVal,0) maplist.width=round(maplist.width/magVal,0) scaleVal='transform: scale('..magVal..') translateY('..tostring((origH-maplist.height)/2)..'px);' else magVal=1 end end -- set up the three nested div boxes (plus an extra if centered) to put the map plus title/caption area, in an appropriate frame on the page if args.float=='center' or args.float=='centre' then table.insert(result,'<div class="center"><div class="thumb tnone">') elseif args.float=='left' then table.insert(result,'<div class="thumb floatleft">') else table.insert(result,'<div class="thumb floatright">') end table.insert(result,'<div class="thumbinner" style="position: relative; top: 0px; right: 0px; width: '..(args.width or "400")..'px;">') if args.title then table.insert(result,'<div class="center" style="font-weight:bold">'..args.title..'</div>') end if magVal and magVal >1 and magVal<=2 then table.insert(result,'<div class="thumbinner noresize" style="display:block; position: relative; outline:0px; border:0px; padding:0px; background-color:transparent; color:inherit;') table.insert(result,'top: 50%; right: '..tostring(0-((origW-maplist.width))/2)..'px; ') table.insert(result,'height: '..origH..'px; width: '..maplist.width..'px; '..scaleVal..'">') else magVal=1 table.insert(result,'<div class="thumbinner noresize" style="position: relative; outline:0px; border:0px; padding:0px;') table.insert(result,'top: 0px; right: 0px; ') table.insert(result,'height: '..maplist.height..'px; width: '..maplist.width..'px ">') end --add any boundary Qvalues to map-data-light if requested if args['geo-region'] and (args['geo-boundaries'] == '1') then local qmaps=args['map-data-light'] or '' if qmaps ~='' and string.sub(qmaps,#qmaps) ~= ',' then qmaps=qmaps..',' end local geoRegion = args['geo-region'] -- or add base-page here local geoSubRegion = args['geo-sub-region'] or 'P150' if not mw.wikibase.isValidEntityId(geoSubRegion) then geoSubRegion = 'P150' end if mw.wikibase.isValidEntityId(geoRegion) then local ct=1 local qtemp while (mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct]) do -- debugmsg('table '..ct..' - '..mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id) qtemp = mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id or nil if qtemp then if mw.wikibase.entityExists(qtemp) then qmaps=qmaps..qtemp..',' -- see if there is a geocode boundary Qvalue to add else debugmsg(mw.addWarning('unable to add geocode value for '..mw.wikibase.getLabel..','..qtemp)) end end ct=ct+1 end end if qmaps and qmaps ~= '' then -- add back the created/expanded list of boundary Qvalues, and remove trailing comma args['map-data-light'] = string.sub(qmaps,1,#qmaps-1) end end -- Create the basemap using mapframe local mapframecontent=getmapframecontent(args,'basemap') table.insert(result, frame:extensionTag{ name ='mapframe', content=mapframecontent, args={width=tostring(maplist.width), height=tostring(maplist.height), zoom=tostring(maplist.zoom), longitude=tostring(maplist.lonbase), latitude=tostring(maplist.latbase), mapstyle=maplist.mapstyle, frameless=true } } ) --Add coverall box to block the unhelpful links from mapframe - which wouldn't include all the dots. Reinstate some links to osm and wikimedia table.insert(result,'<div style="position: absolute;width:'..tostring(maplist.width)..'px; height:'..tostring(maplist.height)..'px;') table.insert(result,'top:0px;left:0px;background-color:#FFFFFF00; color:inherit;"></div>') --Add replacent hover-links for OpenStreetMap and maps terms and conditions table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-13)..'px; width: 12px; height: 12px">') table.insert(result,'[[file:Transparent.svg|12px|link=https://www.openstreetmap.org/copyright|'..aboutOSM..']]</div>') table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-110)..'px;') table.insert(result,'width: 12px; height: 12px background-color: transparent; color:inherit;">') table.insert(result,'[[file:Transparent.svg|12px| link=https://foundation.wikimedia.org/wiki/Policy:Maps_Terms_of_Use|'..termsOfUse..']]</div>') -- Add scale-line if not args.scalemark or args.scalemark~='0' then local top=maplist.height-42 local left=maplist.width-61-(tonumber(args.scalemark or '1')) local minipos=string.lower(args.minimap or '') -- scalemark gets pushed left if it would be behind the minimap if minipos~='' and not(string.find(minipos,'left') or string.find(minipos,'top') ) then local offset=tonumber(args.scalemark or '1')-tonumber(args['mini-width'] or '60') if offset<1 then left=maplist.width-61-tonumber(args['mini-width'] or '60') end end if maplist.width-left >216 then top=top+14 end -- shunt scaleline down if it is beyond the copyright stuff local scalek,scalem=getScale(maplist.zoom, maplist.latbase, magVal) local magReduce='' table.insert(result,"<div style=\"display:inline-block; position: absolute; background-color: #111111") table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;M0,8 l0,4 l20,0 l0,-4 l-0.3,0 l0,3.7 l-19.4,0 l0,-3.7 z&#39;) ") table.insert(result,"; width:20px") -- path is a 20px20px box, and then rescales if magVal==1 then table.insert(result,"; top:"..tostring(top-1).."px") table.insert(result,"; left:"..tostring(left+16).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5)..", "..tostring(1.5)..")") else -- shrink the scalemark to compensate for magnification table.insert(result,"; top:"..tostring(top-(magVal*0.28)).."px") table.insert(result,"; left:"..tostring(left+(16*(magVal*1.15))).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5*(1/magVal))..", "..tostring(1.5*(1/magVal))..")") magReduce= 'scale('..tostring(1/magVal)..')' end table.insert(result,"\"></div>") table.insert(result,'<div style="position: absolute; top: '..tostring(top)..'px; left: '..tostring(left+47)..'px; font-size: 9.5px; line-height: 126%; width: fit-content;') table.insert(result,'color: #444433; background-color: transparent; text-align: right; transform: '..magReduce..' translateX(-100%);">'..scalek..'<br>'..scalem..'</div>') end --Set up the shapeList and dotList tables, to provide data to go on the map local sgNumbers,sgSortable={},{} --s1,s2 sgNumbers["1"]="1" if args.useFormatStyle and args.useFormatStyle=='shortstyle' then shapeList=ParseShapeTypes (shapeList,args,"1") for argindex,argv in pairs(args) do -- build a list of all the numbered sg's that have been used if string.find(argindex,"sg[a-f,n%d]+") == 1 then -- only look through the sga,sgb, sgc,sgd,sge,sgf and sgn args local x=string.match(argindex,"[%d]+") -- find its number if x and not sgNumbers[x] then sgNumbers[x]=x end -- only add if not already found end if string.find(argindex,"sg[a-f]H") == 1 and highlightNum then highlightOption=true end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form for k,v in pairs(sgSortable) do -- work through the sorted list, parsing each set of sg's in turn, from 1 upwards shapeList=ParseShapeTypes (shapeList,args,v) end if highlightOption==true then shapeList=ParseShapeTypes (shapeList,args,'H') end end local dotList,dotresult,dotItemTable,dotGroupList={},{},{},{} if (not args.useFormatStyle) or args.useFormatStyle=='standardstyle' then local dotmax=0 local qset={} for indx,val in pairs(args) do if string.match(indx,'mark%-coord[%d]+') or string.match(indx,'lat[%d]+') or string.match(indx,'geo%-data[%d]+') then dotmax=math.max(dotmax, tonumber(string.match(indx,"[%d]+"))) if string.match(indx,'geo%-data[%d]+') then local argnum=string.sub(indx,9,#indx) or '' if args['mark-title'..argnum] == 'none' or (string.find(args['mark-title'..argnum] or 'x','nolist')== 1) then -- debugmsg('sort values ignored '..argnum..', '..args['mark-title'..argnum]) else qset[val]=getQset (val, (args['geo-data-type'] or 'P1082'), argnum, args) end end end if (indx=='shapeH') or (indx=='shape-colorH') or (indx=='shape-outlineH') or (indx=='label-colorH') then highlightOption=true end end local qlist=args['geo-data-list'] if qlist then if string.sub(qlist,#qlist) ~= ',' then qlist=qlist..',' end else qlist='' end local geoRegion = args['geo-region'] -- or add base-page here local geoSubRegion = args['geo-sub-region'] or 'P150' if not mw.wikibase.isValidEntityId(geoSubRegion) then geoSubRegion = 'P150' end if (args['find-geo-dots'] or '1') ~= '0' and geoRegion and mw.wikibase.isValidEntityId(geoRegion) then local ct=1 local qtemp while (mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct]) do -- debugmsg('table '..ct..' - '..mw.wikibase.getBestStatements(adminRegion,adminSubRegion)[ct].mainsnak.datavalue.value.id) qtemp = mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id or nil if qtemp then qlist=qlist..qtemp..',' -- add subregion Qvalue to the list end ct=ct+1 end end if qlist and #qlist>1 then -- create a table from the csv list local _, c = qlist:gsub(",","") local qtab=splitItem(qlist,c) for xd,yd in pairs(qtab) do yd=string.upper(yd) if qset[yd] == nil and mw.wikibase.isValidEntityId(yd) then dotmax=dotmax+1 qset[yd]=getQset (yd, (args['geo-data-type'] or 'P1082'), tostring(dotmax), args) args['geo-data'..tostring(dotmax)] = yd -- debugmsg(dotmax..' = '..(string.upper(yd) or 'Q Not found')..': '..(mw.wikibase.getLabel(yd) or 'label Not found')..': '..(mw.wikibase.getSitelink(yd) or 'link Not found')) elseif not mw.wikibase.isValidEntityId(yd) then debugmsg(mw.addWarning(yd..' is not a [[wikidata]] page. geo-data-list needs comma-separated Qvalues' )) else debugmsg(mw.addWarning(yd..' is duplicate')) end end end -- qset contains set of Qvalues and can be from wikidata geo-region, list of geo-data-list Q values or individual geo-data1= etc local ts = {} -- table to hold the keys local useQset=false for ky, val in pairs(qset) do table.insert(ts, {key = ky, value = val[1], num=val[2]}) useQset=true end if useQset then local order = string.lower(args['geo-list-sort'] or 'a') -- sort the list from previously stored sortkey, either Lowest or Highest first if string.find(',al,vl,nl,wl,west,w,alpha,a,' , ','..order..',') then table.sort(ts, function (a,b) return lessthan(a.value, b.value) end) else table.sort(ts, function (a,b) return morethan(a.value, b.value) end) end for ky,val in ipairs(ts) do -- assign values from sorted list to numbered parameter args['numbered'..val.num] = ky -- The numbered parameter becomes the value for each dot that has a geo Qvalue, from manual, list or wiki sources. end end dotItemTable=tradstyleParseShapes(args,dotItemTable,dotmax) for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) or args['geo-data'..x] then sgNumbers[x]=x -- add it to the list end end else for indx in pairs(args) do if string.match(indx,'dot[%d]+') then table.insert(dotList,indx) end --add the index name for dot1, dot2 etc to dotList end table.sort(dotList,morethan) for indx,dotName in pairs(dotList) do dotresult=ParseData(args,string.match(dotName,'[%d]+') ) -- using each dot number, assign the settings for each dot to a dotresult item line table.insert(dotItemTable,dotresult) -- and store that item line within the dotItemTable end end for arcVal = 65,91 do -- check through args looking for any arcs local arcLetter=string.char(arcVal) if args['arc-text'..arcLetter] or args['arc'..arcLetter] then makeArcText(args,result,arcLetter) end end local dotdivs='' local ddots=0 if dotItemTable[1] then local ddots=(dotItemTable[1].lat or 0)+(dotItemTable[1].lon or 0) end local fgroup='F'..tostring(maplist.latbase+maplist.lonbase+ddots ) local FullscreenList={} local addcomma='' for i,dotitem in pairs(dotItemTable) do -- working throug each dot item, merge the dot and shape values into a full set of css text local dotgroup= dotitem.group or "0" if dotitem.posType=='mark-line' and dotitem.markDest then --find destination xy values for any mark-lines for n,v in pairs(dotItemTable) do if v.code == dotitem.markDest then dotitem.gridx2=v.gridx dotitem.gridy2=v.gridy break end end end local qtype=dotitem.group -- find which shape group each dot has been assigned --debugmsg('dotgroup='..qtype..', sg='..(sgNumbers[qtype] or 'nil')..' , shapeList='..shapeList[qtype].shape) if not sgNumbers[qtype] then qtype="0" end --shapeList[dotitem.group] will give access to the shape values for that dot if highlightNum==dotitem.code and highlightOption==true and shapeList['H'] then table.insert(result, getshapetable(dotitem,shapeList['H'])) else table.insert(result, getshapetable(dotitem,shapeList[qtype])) -- Add the actual css instructions for each dot end if shapeList[dotgroup] and not string.find((dotitem.param1 or ''),'nomap') then -- only add if not excluded with 'nomap' labelText local ftext='' if dotitem.dotTag~='' and not string.match(dotitem.labelText or '','[%d]') then ftext=stripdivs(dotitem.dotTag or '')..' <br>' end if (dotitem.labelText ~= ftext) and dotitem.dotLink =='' then ftext=ftext..' '..stripdivs(dotitem.labelText)..'<br>' end if (dotitem.dotLink) and (dotitem.dotLink ~='') then ftext=ftext..dotitem.dotLink..'<br>' end if dotitem.imageName then ftext=ftext..'[[File:'..dotitem.imageName..'|250px]]' end table.insert(FullscreenList,1, makeFullscreenItem (string.gsub(ftext,"[\n]+"," "), dotitem.info,round(dotitem.lat,5),round(dotitem.lon,5),fgroup,shapeList[dotgroup].shapeColor)..addcomma ) addcomma=', ' end -- makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) -- Always add to start of list, to reverse the sequence, and separate with commas except for first item, which is now at the end end if args.legendBox then makeLegendBox(result,args) end if args.minimap or args['mini-locator'] then makeLocatorMap(args,result) end -- add tag link and details for fullscreen version addcomma='' if (mapframecontent or '[]') ~= '[]' then addcomma=',' end mapframecontent=getmapframecontent(args,'fullscreen') local contentstart='[ '..mapframecontent..addcomma..'{ "type": "FeatureCollection", "features": [ ' --extra features after first square bracket local contentend=' ] } ]' table.insert(result, '<div style="position: absolute;top: 9px;left: '..tostring(maplist.width-34)..'px">') table.insert(result, '<div style="color: white; opacity:100; font-size: 19px; font-weight:normal; text-align: left;">') table.insert(result, frame:extensionTag{ name ='maplink', content=contentstart..table.concat(FullscreenList)..contentend, args={zoom=tostring(maplist.zoom+1), class='no-icon', frameless='1', latitude=tostring(maplist.latbase), longitude=tostring(maplist.lonbase), --add invisble 'en-spaces' for tooltip text='<div title="'..fullscreenlinktext..'">&nbsp;&nbsp;&nbsp;</div>'} } ) table.insert(result,'</div></div>') -- end of maplink ----- -- add closing div for main map table.insert(result,'</div>') -- collate caption material to go in the outer div class if captionTable ~= '' and (not args['auto-caption']) then args['auto-caption']='on' end --default to include list if using geo values local autocaption=string.lower(args['auto-caption'] or 'no') local autoOff=autocaption:match("(%w+)(.*)") -- select the first word in autocaption and see if it is a negativeAnswer) if negativeAnswer[autoOff] then captionTable='' end if args.caption or (not negativeAnswer[autoOff]) then table.insert(result,'<div class="thumbcaption" style="text-align:left">') if args.caption then table.insert(result,args.caption) if captionTable ~= '' then table.insert(result,'<hr>') end end end local columns=tonumber(autoOff:match("[%d]+") or '1') if columns>1 then columns=round(maplist.width/(columns*17), 0) end -- convert from em to px for historical reasons --for k in pairs(dotList) do capchk=capchk..(args["dotlink"..k] or '') end local capchk=nil local captionList = {} for key, value in pairs(dotItemTable) do -- only add an autocaption line if there is both a dotTag and a dotLink line available and it is not marked as nolist if value.dotTag and value.dotTag~='' and (not string.find(value.param1 or '','nolist')) and string.gsub(value.dotLink or '',"%s+","")~='' then table.insert(captionList, {key = key, value = value}) if captionTable == '' then capchk=true else -- add the next line of content to the html table capchk=false end end end if captionTable ~= '' then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) for k,v in pairs(captionList) do local nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag local ngrp=v.value.group if captionTableText[ngrp] then -- debugmsg('table text for'..v.value.dotTag..', '..captionTableText[v.value.group][1]..', '..captionTableText[v.value.group][2]..', '..captionTableText[v.value.group][3]..' ('..captionTableText[v.value.group][4]..')') local c1,c2 c1,c2=checkColors(shapeList[ngrp].shapeColor) local cDot='<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;' cDot=cDot..'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div>' captionTable = htmlTableLine(captionTable,cDot,captionTableText[ngrp][1], captionTableText[ngrp][2], captionTableText[ngrp][3], captionTableText[ngrp][4]) end end end if capchk and (not negativeAnswer[autoOff]) then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) -- local myDivision = string.gsub((args.toggletext or toggletext), "%s+", "") if string.find(autocaption,'collaps') then table.insert(result,'<div class = "wikitable mw-collapsible ') if string.find(autocaption,'collapsed') then table.insert(result,'mw-collapsed') end table.insert(result,'" style="width:100%; line-height: 17px; border-style:none; text-align: center"><b>'..(args.toggletext or toggletext)..'</b>') end if string.find(autocaption, 'columns=') then columns=string.match(autocaption,'[%d]+',string.find(autocaption, 'columns=') ) end table.insert(result,'<div class="mw-collapsible-content" style="column-count:'..columns..'; column-rule:solid 1px;text-align:left;padding-top:5px">') table.sort(dotList,lessthan) local nval,ngrp='','0' for k,v in pairs(captionList) do nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag ngrp=v.value.group or '0' if v.value.dotLink and v.value.dotLink~='' and nval and nval~='' then local c1,c2 if nval==args.highlight then c1,c2=checkColors(shapeList['H'].shapeColor) else c1,c2=checkColors(shapeList[ngrp].shapeColor) end table.insert(result,'<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;') table.insert(result,'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div> '..v.value.dotLink..'<br>') end end table.insert(result,'</div>') -- end for caption-content div if string.find(autocaption,'collaps') then table.insert(result,'</div>') end -- end for toggle frame end if captionTable ~= '' then table.insert(result,captionTable..'</table>') end if args.caption or (not negativeAnswer[autoOff]) then table.insert(result,'</div>') end -- end for whole caption frame table.insert(result,'</div></div>') -- outer two frames if args.float == 'center' or args.float=='centre' then table.insert(result,'</div>') end if args['show-new-format'] == 'hints' then -- provide a 'format hint panel' in the 'Preview Box' local w="<small>Below are some template hints for the 'sga' compressed version, " w=w..'{{tl|OSM Location dots}}. It can use these, instead of the more verbose {{tl|OSM Location map}} parameter format. ' w=w..'Data is divided between a "ShapeGroup" and the "Dots", so that a single shapeGroup can be used for multiple dots on the map. ' previewMsg(w..'(nb. the "group" value can be the number or an assigned name of a shapeGroup)</small>') w='<small>{{tl|OSM Location dots}}: | dot(n)=group,lat,lon,dotTag | ' w=w..'dotlink=link/tooltip | dotlabel=label,position,dx,dy,param1,info | dotpic=filename <br>' w=w.."(nb. param1 options include 'nolink' 'nolist' 'nomap' 'hemisphere-1' 'hemisphere+1', 'noline' - quotes not required, separate with spaces).<br>" w=w..'| sga = Shape,Sizepx,Color,Angledeg | sgb= OutlineWidth,Color,Style | sgc=TextSize,Color,Angle,bold italic <br>' w=w..'| sgd=TagSizepx,Color,Spacer,Angledeg | sge=LineWidth,Color,Style | sgf=TextSpacingpx,LineHeight%,Outlinepx,backgroundColor<br>' w=w..'| sgn=Name (optional, to assign a meaningful name) | sgp=Parent (can be the name or number of the parent shapeGroup. ' previewMsg(w..'Each shapegroup will inherit values from a parent, stretching back to "sga1" and its default values.)</small>') if #pmsg then local dbg={} for i,x in pairs(pmsg) do table.insert(dbg,x..'<br>') end dbg=mw.addWarning(table.concat(dbg)) table.insert(result, dbg) end end if args.coordtest then debugmsg(mw.text.nowiki(args.coordtest)) end for i,x in pairs(msg) do table.insert(result, x..'<br>') end return table.concat(result) end function p.main(frame) local args = getArgs(frame) local itemTab={} maplist.width=tonumber(args.width) or 400 maplist.height=tonumber(args.height) or 300 if args.coord then itemTab=splitItem(convertCoordsTrad (args.coord),2) maplist.latbase=itemTab[1] maplist.lonbase=itemTab[2] else maplist.lonbase=tonumber(args.lon) or 5 maplist.latbase=tonumber(args.lat) or 0 end maplist.zoom=tonumber(args.zoom) or 1 visibleLinks=args.showlinks highlightNum=args.highlight if args.nolabels=='1' then maplist.mapstyle='osm' else maplist.mapstyle='osm-intl' end if maplist.width >= 400 and not(args['show-q-values']) then args['show-q-values'] = '1' -- default to on if wider than 400px elseif maplist.width < 320 then args['show-q-values'] = '0' end -- turn off Qvalus if frame is too narrow, even if requested return p._main(args) end return p hjrc3ne0gokrndtkymcmms9c560jqof 204458 204453 2026-07-05T14:49:50Z HaxelKaszëba 17492 204458 Scribunto text/plain require('strict') local delink=require('Module:Delink').delink local getArgs = require('Module:Arguments').getArgs local p = {} local maplist={} local sgNames={} local highlightOption=false local highlightNum local visibleLinks local geoTotalPop = 0 local captionTable = '' local captionTableText = {} -- This module creates framed maps of anywhere in the world, at the required scale, and enables annotations, -- dots, shapes, lines and other ways to customise the area of the map being shown. It also provides a link -- to an interactive fullscreen version, which has locator dots instead of annotations and shapes. -- This is the 2025 successor module to a wiki-markup template version of 2024, which itself was a successor -- to the 'Graph'/VEGA driven template that was begun in 2016, until the Vega version was switched off in 2023. -- This module is called from template {{OSM Location map}}, which uses the same parameter formats as before. -- In addition it may at some point be possible to use a more concise parameter format using the template {{OSM Location dots}} -- In general the css output from the two formats will be identical, but the the concise version will allow bits of -- greater control over some of the settings. -- see the documentation on the two template pages for details of how to use the mapping features. -- If language customisation is needed, there are text items below that can be translated. Also see the color table -- below with details of how to add additional color names to allow localised alternatives. -- (Translating other language shape-types could be possible, but has not currently been contemplated. -- Parameter name translation would be harder but likely to be possible, ideally still retaining compatibility -- with template calls already written using English). -- -------------- the following text variables are used within the template, and can be translated as required --------------- -- -------------- you may wish to retain the English version as a comment, to keep track of the original phrase -------------- local negativeAnswer={no=1,'0'-1,off=1} local fullscreenlinktext = 'Click for interactive fullscreen map with links to nearby articles' local toggletext = 'List of numbered map items:' local termsOfUse = 'Maps: terms of use' local aboutOSM = 'About OpenStreetMaps' local yards = 'yds' local miles = 'miles' local metres = 'm' local km = 'km' local sqkm = 'sq.km' local year = 'year' local notFound = 'not found' local totalItems = 'Total' local sumOfDatapoints = 'sum of datapoints' local numberCol = 'No.' -- abbreviated for top of column of code numbers local wikidataLink = 'Wikidata page' local shapeList={} --This sets up the 'factoryDefault' shape group 0 shapeList["0"]={shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border of the shape outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items text settings for labels textSZ="11px", textCL="darkgrey", textNG="0deg", --sgf further text settings textSP="0px", textLH="120%", textOL="0px", textBG="transparent", --sgd items for dotTag text settings tagSize="10px", tagColor="white", tagSpacer="0px", tagAngle="0deg", --sge items for extension line to connect label to dot textEW="0px", textEC="darkgrey", textES="solid" } local colorList={} -- used by colorLookup to catch unsupported colors (eg 'LimeGreen'), to convert to generic version colorList['green']='hardgreen' -- it could also be added to to include alternative language equivelants, for a quick solution. colorList['red']='hardred' -- colorList ['source'] = target colorList['white']='white' -- converts any color that includes 'source' into its equivelent target colorList['blue']='hardblue' -- note, for translation you can add to this list, rather than replace it, colorList['brown']='brown' -- which would mean existing map definitions in english would also still work, alongside translated ones colorList['grey']='hardgrey' colorList['gray']='hardgrey' colorList['purple']='hardpurple' colorList['orange']='hardorange' colorList['leaf']='hardleaf' --for a more thorough translation, you can add all the variants of the colors as further CTB elements and hex values or redirects local CTB={} -- set up a table of color names (the CTB Color table index) and html hash colorhex values. CTB["paleblue"],CTB["softblue"],CTB["hardblue"],CTB["darkblue"]="#D6E1EC","#77A1CB","#4B77D6","#1c559e" CTB["palered"],CTB["softred"],CTB["hardred"],CTB["darkred"] = "#FCC6C0","#EC644B","#DB3123","#AA1205" CTB["palegreen"],CTB["softgreen"],CTB["hardgreen"],CTB["darkgreen"]= "#D2F0E5","#81AF81","#269F46","#0b7527" CTB["paleleaf"],CTB["softleaf"],CTB["hardleaf"],CTB["darkleaf"]= "#dff5c1","#b5e376","#8cc244","#679c21" CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"]= "#E8E8D6","#AAAA88","#777755","#444433" CTB["palegray"],CTB["softgray"],CTB["hardgray"],CTB["darkgray"]=CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"] CTB["palebrown"],CTB["softbrown"],CTB["hardbrown"],CTB["darkbrown"]="#FAF6ED","#CCB56C","#AD7F14","#754910" CTB["palepurple"],CTB["softpurple"],CTB["hardpurple"],CTB["darkpurple"]="#e0d1e6","#c784e0","#a029cf","#7a05a8" CTB["paleorange"],CTB["softorange"],CTB["hardorange"],CTB["darkorange"]="#ffedc2","#ffcf61","#EEB533","#e39f05" CTB["black"],CTB["white"],CTB["yellow"]="#000000","#FFFFFF","#FAF039" CTB["background"],CTB["paleground"],CTB["beigeground"]="#f9f6f2","#FEFEFA","#F5F5DC" CTB["beige"]=CTB["beigeground"] CTB["aqua"],CTB["teal"],CTB["fuchsia"] = "#00FFFF","#008080","#FF00FF" CTB["maroon"],CTB["olive"],CTB["navy"] = "#800000","#808000","#000080" CTB["lime"],CTB["limegreen"],CTB["aquamarine"] = "#00FF00","#32CD32","#7FFFD4" CTB["silver"],CTB["yellow"],CTB["orchid"] = "#800000","#FFFF00","#DA70D6" -- set up a table of predefined clip-paths local pathshape={} pathshape.squaredd = "M 19,1.25 l 0,18 -18,0 0,-18 18,0m-1,1 -16,0 0,16 16,0 0,-16m-1,1 0,14 -14,0 0,-14 14,0zm-1,1 -12,0 0,12 12,0 0,-12zm-1,1 0,10 -10,0 0,-10 10,0z" pathshape.squared = "M 18,2.5 l 0,15 -15,0 0,-15 15,0m-1,1 -13,0 0,13 13,0 0,-13zm-1,1 0,11 -11,0 0,-11 11,0z" pathshape.triangledd="M 0 20,20 20,10 0,0 20ZM1.5 19,10 1.7,18.5 19,1.5 19ZM3 18,17 18,10 3.8,3 18ZM4.5 17,10 5.4,15.4 17, 4.5,17ZM6 16,13.8 16,10 7.4z" pathshape.triangled ="M1,18 l 18,0 l -9,-18 l -9,18zm1.7,-1.1 l 7.3,-14.6 l 7.3,14.6 l -14.6, 0zm1.7,-1 l 11.0,0 l -5.5,-11 l -5.5,11z" pathshape.circledd = "M0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm0.8,0a9.2,9.2 0 1,1 18.4,0a9.2,9.2 0 1,1 -18.4,0m1,0a8.2,8.2 0 1,0 16.4,0a8.2,8.2 0 1,0 -16.4,0zm0.8,0a7.2,7.2 0 1,1 14.8,0a7.2,7.2 0 1,1 -14.8,0m1,0 a6.4,6.4 0 1,1 12.8,0a6.4,6.4 0 1,1 -12.8,0z" pathshape.circled = "M2.5,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0m0.8,0 a5,5 0 1,1 11.4,0a5,5 0 1,1 -11.4,0" pathshape.diamond = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10z" pathshape.diamondd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm1,0 l 6,8.5 l 6,-8.5 -6,-8.5 -6,8.5zm1,0 l 5,-7 5,7 -5,7 -5,-7z" pathshape.diamonddd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm0.75,0 l 6.25,9 l 6.25,-9 -6.25,-9 -6.25,9zm0.75,0 l 5.5,-8 5.5,8 -5.5,8 -5.5,-8zm0.75,0 l 4.75,7 l 4.75,-7 -4.75,-7 -4.75,7zm0.75,0 l 4,-6 4,6 -4,6 -4,-6z" pathshape.crossd = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0zM2.3,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.cross = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0z" pathshape.thincross = "M2,12 l6,0 l0,6 l4,0 l0,-6 l6,0 l0,-4 l-6,0 l0,-6 l-4,0 l0,6 l-6,0z" pathshape.fivepointstar = "M10 0 L12.245 6.91 19.511 6.91 13.633 11.18 15.878 18.09 10 13.82 4.122 18.09 6.367 11.18 0.489 6.91 7.755 6.91Z" pathshape.fivepointstard = "M10 1.5 L 11.90825 7.3735 18.08435 7.3735 13.08805 11.003 14.9963 16.8765 10 13.247 5.0037 16.8765 6.91195 11.003 1.91565 7.3735 8.09175 7.3735 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sixpointstar = "M10 0 L12.323 5.977 18.66 5 14.645 10 18.66 15 12.323 14.023 10 20 7.677 14.023 1.34 15 5.355 10 1.34 5 7.677 5.977Z" pathshape.sixpointstard = "M10 1.5 L 11.97455 6.58045 17.361 5.75 13.94825 10 17.361 14.25 11.97455 13.41955 10 18.5 8.02545 13.41955 2.639 14.25 6.05175 10 2.639 5.75 8.02545 6.58045 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sevenpointstar = "M10 0 L12.048 5.747 17.818 3.765 14.602 8.95 19.749 12.225 13.69 12.943 14.339 19.01 10 14.72 5.661 19.01 6.31 12.943 0.251 12.225 5.398 8.95 2.182 3.765 7.952 5.747Z" pathshape.sevenpointstard = "M10 1.5 L11.7408 6.38495 16.6453 4.70025 13.9117 9.1075 18.28665 11.89125 13.1365 12.50155 13.68815 17.6585 10 14.012 6.31185 17.6585 6.8635 12.50155 1.71335 11.89125 6.0883 9.1075 3.3547 4.70025 8.2592 6.38495 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.eightpointstar = "M10 0 L11.88 5.46 17.071 2.929 14.54 8.12 20 10 14.54 11.88 17.071 17.071 11.88 14.54 10 20 8.12 14.54 2.929 17.071 5.46 11.88 0 10 5.46 8.12 2.929 2.929 8.12 5.46Z" pathshape.eightpointstard = "M10 0 L10 1.5 L11.598 6.141 16.01035 3.98965 13.859 8.402 18.5 10 13.859 11.598 16.01035 16.01035 11.598 13.859 10 18.5 8.402 13.859 3.98965 16.01035 6.141 11.598 1.5 10 6.141 8.402 3.98965 3.98965 8.402 6.141ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.ring="M2.6,9.5a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.semicircle = "M0,5 C0,18.3,20,18.3,20,5 L 0,5z" pathshape.arrowhead = "M 10,10.2 l 10,2.8 -2,-3 2,-3 -10,2.8 -0.2,0.2z" pathshape.boxd=pathshape.squared pathshape.boxdd=pathshape.squaredd pathshape.ellipsed=pathshape.circled pathshape.ellipsedd=pathshape.circledd local msg={} local function debugmsg(txt) table.insert(msg,txt) end local pmsg={} local function previewMsg(txt) table.insert(pmsg,txt) end local function colorLookup(color) for c,l in pairs(colorList) do if string.find(color,c) then return l end end return color end local function getColor (color,chk) local c local opacity="100" if not color or color=='' then color='hardgrey' end if color=="transparent" then return color end if color=="background1" then color='background' end if string.byte(color,1,1)==35 and (#color == 7 or #color == 9) then c=color elseif string.byte(color,1,1)==35 and #color == 4 then c=string.sub(color,1,2)..'f'..string.sub(color,3,3)..'f'..string.sub(color,4,4)..'f' else local s=color..'1' s= s:sub(0,s:find("%d")-1) opacity=string.match(color,"%d+") if not CTB[s] then s= colorList[s] end -- check for synonyms and translations if not CTB[s] then debugmsg(mw.addWarning('color = '..color..'. The color name is not defined. Used default grey instead')) end c=CTB[s] or CTB.hardgrey end if opacity and (tonumber(opacity) < 100) and string.find(c,"#")==1 and string.len(c)==7 and opacity~="" then local hexval=string.format("%x",(math.floor(tonumber(opacity)*2.55))) c=c..hexval end return c end function p.colorvalue(frame) -- enable external access to the CTB colorTable values. usage: {{#invoke:OSM Location map|colorvalue|color=hard blue}} local c if not frame.args.color or frame.args.color=='' then c='grey' else c=string.lower(string.gsub(frame.args.color,'%s+','')) end return string.upper(string.sub(getColor(c),2)) end local function checkColors(color) local c=getColor(color,'check') local opacity =1 -- calculate colour brightness and return black or white for contrast if c=='transparent' then return c,'#000000',0 end if not (string.find(c,'#')==1) then return c,'#FFFFFF',0 end if #c>8 then opacity= tonumber('0x'..(string.sub(c,8,9)))/500 end local r=tonumber('0x'..(string.sub(c,2,3)))/255 local g=tonumber('0x'..(string.sub(c,4,5)))/255 local b=tonumber('0x'..(string.sub(c,6,7)))/255 if 0.2126 * r + 0.7152 * g + 0.0722 * b / opacity < 0.7 then return c,'#FFFFFF',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity else return c,'#000000',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity end end local function morethan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) and tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%-?%f[%d]%d[,.%d]*%f[%D]') ) b = tonumber(string.match(b, '%-?%f[%d]%d[,.%d]*%f[%D]') or '0') end return a>b end local function lessthan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) and tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%-?%f[%d]%d[,.%d]*%f[%D]')) b = tonumber(string.match(b, '%-?%f[%d]%d[,.%d]*%f[%D]') or '0') end return a<b end local function getsize(size) --size1 is between 1 and 3 values, each with px, equating to width,height,corner-rounding --eg '15px 25px 5px' (spaces are optional) or '18px'. returns three numbers local sizeval = {} for v in string.gmatch(size, "[^px]+") do table.insert(sizeval,v) end sizeval[1] = tonumber(sizeval[1]) or 13 sizeval[2] = tonumber(sizeval[2]) or sizeval[1] sizeval[3] = tonumber(sizeval[3]) or 0 return sizeval[1],sizeval[2],sizeval[3] end local function coord2text(coord) -- looks through the output from {{coord}} to find the lat and long decimal values -- and converts compass points to minus or not-minus, return with separating comma. local lat = string.match(coord,'[%.%d]+°[NS]') local lon = string.match(coord,'[%.%d]+°[EW]') local neg={N="",S="-",W="-",E=""} return neg[string.match(lat, '[NS]')]..string.match(lat,'[%.%d]+')..","..neg[string.match(lon, '[EW]')]..string.match(lon,'[%.%d]+') end local function convertCoordsTrad (row) local coords='' if row and string.find (row,'<span class="geo">') then local a,b=string.find(row,'<span class="geo">') local start=b+1 a,b=string.find(row,"</span>",b) local finish=a-1 coords=string.sub(row,start,finish) coords=string.gsub(coords,'; ',',') end return coords end local function convertCoords (row) local start,finish,lat,lon,coords,says if row then local a,b=string.find(row,"<span class=") start=a while a do -- find the final span> finish=b a,b=string.find(row,"span>",b) end if start then coords= string.sub(row,start,finish) says="" if string.find(coords,'<span class="error">') then error("coord error: badly formed coordinates",0) end coords=coord2text(coords) coords = string.sub(row,1,start-1)..coords..string.sub(row,finish+1) else coords=row end return coords else return "Nothing to see here" end end local function parseCoordFrame(coord) if not coord then return nil, nil end local lat, lon = string.match(coord, "{{coord|([%-%.%d]+)|([%-%.%d]+)}}") if lat and lon then return tonumber(lat), tonumber(lon) end lat, lon = string.match(coord, "([%-%.%d]+),%s*([%-%.%d]+)") if lat and lon then return tonumber(lat), tonumber(lon) end return nil, nil end local function fillCommas(val,max) local line='' if not val then line=',' -- ensure there is some content else line = val --string.lower(string.gsub(val,"%s+","")) -- or strip spaces end if string.find(line,',') == 1 then line=' '..line end -- ensure initial comma is not skipped local _, count=string.gsub(line,",","") -- add enough subsequent commas for all entries line=line..string.rep(',',max-count) while(string.find(line,",,") ) do line=string.gsub(line,",,",", ,") --ensure string.gmatch doesn't ignore any empty items by padding with spaces end return line end local function makeLinkBox(left,top,wid,label, link) local linkBoxName='Transparent square.svg' if visibleLinks or '' =='yes' then linkBoxName='Red hollow square.svg' end local builder = mw.html.create('div') --display:inline-block; local t= string.find(label,'wikidata') -- exclude wikidata from tooltip line if t then label=string.sub(label,1,t-2) -- don't show wikidata value in tooltip if string.find(link,'wikidata') then link = '' end -- don't link on click to wikidata (if there was not another link) end builder :cssText('position:absolute;left:'..tostring(left-1-wid/2)..'px;top:'..tostring(top-1 + math.min(wid/2-12,0) - wid/2)..'px') :wikitext(string.format( '[[File:%s|%dpx|link=%s|%s]]', linkBoxName, wid+2, link, label )) return tostring(builder) end local function extractItem(row,searchItem) -- remove text following a searchItem or start of line, which might be in quote-marks to allow commas local xend,xstart=1,0 if not row then return '','' end if searchItem then xend,xstart= string.find(row or '',searchItem or 'image:') end if not xstart then return string.gsub(string.gsub(row or '',"%b\"\"", ''),"%b\'\'", '') or '','' end while row:byte(xstart+1) == 32 and xstart<#row do -- skip over any leading spaces xstart=xstart+1 end local xbyte=row:byte(xstart+1) if xbyte == 34 or xbyte == 39 then -- are they wrapped in single or double quotes xstart=xstart+1 xend=row:find(string.char(xbyte),xstart+1) else xend = row:find(',',xstart+1) -- if no quotes, we assume no commas if not xend then xend=#row+1 end end -- return residual row and extracted text return row:sub(0,xstart)..row:sub(xend), row:sub(xstart+1,xend-1) end local function itemCheck(item,ext) if not item then return nil end if not ext then ext='' end return (string.match(item,"[%.%-?%d]+") or '0')..ext end local function stripdivs(line) return string.gsub(line or '',"%b<>", ' ') end local function splitItem(item,max) -- takes a commas-sep list and returns a table of lowercase items with no spaces, or nil local r={} local x=1 item=string.lower(fillCommas(item,max)) for t in string.gmatch(item,"[^,]+") do r[x]=string.gsub(t,"%s+","") if r[x]=='' then r[x]= nil else -- residual items might have commas if x>max then r[max]=(r[max] or '')..', '..r[x] end end x=x+1 end return r end local function ParseShapeTypes (result,args,sgval) -- for use with compressed, comma-separated 'sg plus dots' parameters --[[ shape table items and default values as set at top of page shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items label text textSZ="11px", textCL="white", textNG="0deg", textAT=attributes ("bold" and/or "italic") --sgd items for dotTag tagSize="11px", tagColor="darkgrey", tagSpacer="0px", tagAngle="0deg", --sge extension line textEW="1px", textEC="darkblue", textES="solid" --sgf fx for text labels textSP="0px" textLH="100%" textOL="1px", textBG="paleground", <!--| sga = shape,Size,Color,Angle|sgb= outlineWidth,color,style eg: sga1=circle,14px,blue,0deg| sgb1=0px,dark grey,solid | sgc=textSize,color,angle,bold/italic | sgd=tagSize,Color,Spacer,Angle eg: sgc1=11px,dark grey,0deg,normal| sgd1=9px,white,0px,0deg | sge=lineWidth,color,style |sgf=textspacing,lineHeight%,outlinepx,backgroundcolor, [bold,italic] eg: sge1=0px,black, solid| sgf1=0px,120%,0px,background | dot=shape-group/lat/lon/title/dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> --]] if args["sgn"..sgval] then local sgname=string.match(args["sgn"..sgval],"(%w+)(.*)") sgNames[sgname]=sgval end local parent= args["sgp"..sgval] if parent then parent=string.match(parent,"(%w+)(.*)") local pos= string.find(parent,"%d+") if pos == 1 then parent=string.match(parent,"%d+") else parent=sgNames[parent] or '1' end end if sgval~='H' then if not parent or tonumber(parent) > tonumber(sgval) then if sgval=="1" then parent="0" else parent="1" end end end local itemTab, line, filename result[sgval]={} result[sgval].shapeType=sgval line,filename=extractItem(args['sga'..sgval] or '','image:') if sgval=='1' and not args.sga1 then line='circle,12px,blue,0deg' end -- ensure there is a parent=1 sga result[sgval].shapeFile=filename or '' -- sga= Attributes for shape itemTab=splitItem(line,4) result[sgval].shape = itemTab[1] or result[parent].shape result[sgval].shapeSize=itemTab[2] or result[parent].shapeSize result[sgval].shapeColor=itemTab[3] or result[parent].shapeColor result[sgval].shapeAngle=itemCheck(itemTab[4],'deg') or result[parent].shapeAngle -- sgb= Border outline attributes for shape itemTab=splitItem(args['sgb'..sgval],3) result[sgval].outlineWidth=itemCheck(itemTab[1],'px') or result[parent].outlineWidth result[sgval].outlineColor=itemTab[2] or result[parent].outlineColor result[sgval].outlineStyle=itemTab[3] or result[parent].outlineStyle --sgc=character attributes for label itemTab=splitItem(args['sgc'..sgval],4) result[sgval].textSZ=itemCheck(itemTab[1],'px') or result[parent].textSZ -- size of text in px result[sgval].textCL=itemTab[2] or result[parent].textCL -- colour for text result[sgval].textNG=itemCheck(itemTab[3],'deg') or result[parent].textNG -- Angle for text result[sgval].textAT=itemTab[4] or result[parent].textAT -- attributes bold, and/or italic --sgd=dotTag attributes itemTab=splitItem(args['sgd'..sgval],4) result[sgval].tagSize=itemCheck(itemTab[1],'px') or result[parent].tagSize result[sgval].tagColor=itemTab[2] or result[parent].tagColor result[sgval].tagSpacer=itemCheck(itemTab[3],'px') or result[parent].tagSpacer result[sgval].tagAngle=itemCheck(itemTab[4],'deg') or result[parent].tagAngle --sge= extension line attributes itemTab=splitItem(args['sge'..sgval],4) result[sgval].textEW=itemCheck(itemTab[1],'px') or result[parent].textEW -- width result[sgval].textEC=itemTab[2] or result[parent].textEC -- colour result[sgval].textES=itemTab[3] or result[parent].textES -- style --sgf= fx for label text itemTab=splitItem(args['sgf'..sgval],4) result[sgval].textSP=itemCheck(itemTab[1],'px') or result[parent].textSP -- spacing value for letters result[sgval].textLH=itemCheck(itemTab[2],'%') or result[parent].textLH -- Angle for text result[sgval].textOL=itemCheck(itemTab[3],'px') or result[parent].textOL -- width of text-border line result[sgval].textBG=itemTab[4] or result[parent].textBG -- color for text background return result end local function round(x,dec) -- x=number [, dec=integer] returns numeric value with upto dec decimals (all but first trailing zeros get truncated) if (dec or 0)==0 then return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5) --this avoids .0 where dec=0 end dec =10^(dec) return x>=0 and math.floor(x*dec+0.5)/dec or math.ceil(x*dec-0.5)/dec end local function maptogrid(t, r, coord, test) t.lat, t.lon = parseCoordFrame(coord) if not t.lat or not t.lon then error("maptogrid: cannot parse coord → " .. tostring(coord)) end t.width = tonumber(t.width) or 0 t.height = tonumber(t.height) or 0 t.lonbase = tonumber(t.lonbase) t.latbase = tonumber(t.latbase) t.zoom = tonumber(t.zoom) or 0 local zoom = t.zoom local x = t.width / 2 + ( ( (math.rad(t.lon) * 6378137) - (math.rad(t.lonbase) * 6378137) ) / (156543.03 * math.cos(math.rad(t.latbase)) / (2 ^ zoom)) ) * (1 - (0.075 * (math.abs(t.latbase) / 90))) local y = t.height / 2 + ( ( (math.log(math.tan(math.rad(t.latbase) / 2 + math.pi / 4)) * 6378137) - (math.log(math.tan(math.rad(t.lat) / 2 + math.pi / 4)) * 6378137) ) / (156543.03 * math.cos(math.rad(t.latbase)) / (2 ^ zoom)) ) * (1 - (0.075 * (math.abs(t.latbase) / 90))) return round(x, r), round(y, r) end local function getScale(zoom, lat, magVal) zoom = tonumber(zoom) or 0 lat = tonumber(lat) if not lat then lat = 0 end if magVal then magVal = tonumber(magVal) if magVal and magVal > 1 and magVal <= 2 then zoom = zoom + (magVal - 1) end end local dist = (156543.03 * math.cos(math.rad(math.abs(lat))) / (2 ^ zoom)) / 17 local y if dist < 1 then y = round(dist * 10, 1) return tostring(y * 100) .. metres, tostring(round(y * 109, 0)) .. yards elseif dist < 18 then y = round(dist, 0) return tostring(y) .. km, tostring(round(y * 0.621371, 1)) .. miles elseif dist < 500 then y = round(dist / 10) return tostring(y * 10) .. km, tostring(round(y * 6.21371, 0)) .. miles else y = round(dist / 100) return tostring(y * 100) .. km, tostring(round(y * 62.1371, 0)) .. miles end end local function ParseData (args,dotval) -- for use with compressed, comma-separated 'sg plus dots' parameters -- takes a structured series of comma-separated items which get parsed as the following: -- dot(n)= (sgNumber or Name),{{coord}} or (lat,lon), (dotTag) -- dotlink(n) = single-parameter text to give wikilink and/or title used by tootlip, fullscreen dots and autocaption list -- dotlabel(n) = 'label text',pos(left,roght,top,bottom,centre,auto),(dx), (dy) pixel offsets, params, info -- dotpic(n) = single parameter wikimedia filename for an image to use in photopanel and/or fullscreen dots -- dotfeature(n)= 'mark-line' (,linewidth,style,gap) or 'photo-panel' (,image-dim,panel-width,panel-height), draws line to n-1 -- label is used if either a position and/or an x,y offset are not 0,0 ( if no label then dotTag will be put at at the x,y offset, or over the dot -- label text can be autoaligned if x,y puts it left or right of the dot, or centered if above/below) -- quote marks are not needed unless including commas within the label text -- param1 is optional items separated by spaces, and can include [nolabel nolist nomap hemisphere+1 hemisphere-1] -- info is free wikitext, to be used in the fullscreen dot box. (use dotpic to show a picture) --<!--| dotx=shape-group,[lat,lon or {{coord}} ], dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> local result={} local count=1 local row = convertCoords (args["dot"..dotval]) -- swap in any {{coord}} values so they are csv lat and lon row=fillCommas(row,4) result.code=dotval -- store the parameter name as the id code for item in string.gmatch(row,"[^,]+") do -- iterate through 'row', adding each csv item in turn, if present if count==1 then --see if it is a number or a name local pos= string.find(item,"%d+") if pos == 1 then result.group=string.lower(string.gsub(item,"%s+","")) else item=string.match(item,"(%w+)(.*)") -- ensure just a single word result.group=sgNames[item] end elseif count==2 then result.lat=tonumber(string.match(item,"[%.%-?%d]+")) or 0-- find the number, with no non-numeric stuff elseif count==3 then result.lon=tonumber(string.match(item,"[%.%-?%d]+")) or 0 elseif count==4 then result.dotTag=item:match( "^%s*(.-)%s*$" ) or "" -- dotTag allows for internal spaces, but no commas end count=count+1 end row, result.labelText= extractItem(args["dotlabel"..dotval]) result.labelText= string.gsub(result.labelText,"[%^]+","<br>") -- convert hats to line breaks local item=splitItem(row,6) result.labelPos=item[2] or 'center' result.dx=tonumber(string.match(item[3] or '0',"[%.%-?%d]+")) or 0 result.dy=tonumber(string.match(item[4] or '0',"[%.%-?%d]+")) or 0 result.param1=string.lower(item[5] or '') if string.find(result.param1,"hemisphere-1",1,true) then result.lon=result.lon-360 elseif string.find(result.param1,"hemisphere+1",1,true) then result.lon=result.lon+360 end local txt = '' if item[6] then -- ensure all info elements are included, including commas count=1 local max=6 for t1 in string.gmatch(fillCommas(row,max),"[^,]+") do if count>max then txt=txt..',' end if count>=max then txt=txt..t1 end count=count+1 end end result.info=txt result.imageName = args['dotpic'..dotval] -- Get first wikilinked item (if any) from the args.dotlink and set this plus the delinked text local testx=args["dotlink"..dotval] or '' result.dotLink=testx if testx ~= '' then testx=stripdivs(testx) result.title=delink({ testx }) local linkstart= string.find(testx,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then result.dlink=delink( { string.sub(testx,linkstart,string.find(testx,']]',1,true)+1),wikilinks='target' } ) else result.dlink='' end else result.dlink='' result.title='' end if args['dotfeature'..dotval] then local item=splitItem(args['dotfeature'..dotval],6) if (item[1] or '') =='photo-panel' then result.ppwidth= tonumber(string.match((item[3] or '110'),"%d+")) -- panel width result.ppheight= tonumber(string.match((item[4] or '48'),"%d+") ) --panel height result.photowidth=round(tonumber(string.match((item[2] or '1.3'),"[%.%-?%d]+")) * result.ppheight+1,0) -- dimension to set image size result.photoImage=result.imageName result.posType='photo-panel' elseif (item[1] or '') == 'mark-line' then local x=tonumber(dotval or '0') result.markDest=item[5] or tostring(x-1) result.mlWidth= tonumber(string.match((item[2]) or '',"%d+") or '1') result.mlStyle= item[3] or 'solid' result.mlGap=tonumber(string.match((item[4] or ''),"[%d]+") or '0') result.posType='mark-line' end --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end maplist.lon=result.lon maplist.lat=result.lat result.gridx, result.gridy = maptogrid(maplist,1) -- convert geo coords to grid xy - using values from maplist table return result end local function multiCheck (args, argName, argVal, defVal, alt) if not alt then alt='nonexistant' end if argVal=='H' and not args[argName..'H'] then argVal=highlightNum or '1' end if argVal=='' then return (args[argName] or args[alt] or (args[argName..'D']) or args[alt..'D'] or defVal) -- unnumbered args do not inherit from D or 1 else return (args[argName..argVal]) or (args[alt..argVal]) or (args[argName..'D']) or (args[alt..'D']) or (args[argName..'1']) or (args[alt..'1']) or defVal end end local function htmlTableLine (htmlTable,h1,h2,h3,h4,h5,type) if type == 'header' then if (h4 or '') ~= 'no-year' then h4='<th class="unsortable">'..h4 or ''..'</th>' else h4 = '' end if (h5 or '') ~= '' then h5='<th class="unsortable">'..h5..'</th>' else h5 = '' end return htmlTable..'<tr><th>'..(h1 or '')..'</th><th>'..(h2 or '')..'</th><th>'..(h3 or '')..'</th>'..h4..h5..'</tr>' else if (h4 or '') ~= 'no-year' then h4='<td>'..h4 or ''..'</td>' else h4 = '' end if (h5 or '') ~= '' then h5='<td>'..h5..'</td>' else h5 = '' end return htmlTable..'<tr><td>'..(h1 or '')..'</td><td>'..(h2 or '')..'</td><td>'..(h3 or '')..'</td>'..h4..h5..'</tr>' end end local function formatInt(number) if number == 0 then return notFound end local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)') int = int:reverse():gsub("(%d%d%d)", "%1,") return minus .. int:reverse():gsub("^,", "") .. fraction end local function convertToSqKm (value,punit) if string.find(punit, 'Q35852') then value = value / 100 -- convert to sq km if it's hectares elseif string.find(punit, 'Q232291') then value = value /(1/2.59) -- or this if it's square miles elseif string.find(punit, 'Q81292') then value = value / 247.1 -- or this if it's acres end return value end local function assignTradstyleShape(shapeResult,default,dotResult,args,nval) local item,itemTab local autoDotTag='' local shapeWidth,shapeHeight=0,0 local argval=nval -- to catch the unnumbered shape series local qvalue='' if argval=='0' then argval='' end if nval=='H' then shapeResult.H={} end -- Assign autovalues if a wikidata Qvalue has been supplied -- some preliminary documentation -- geo-data-type= default is P1082 (population) -- other options might include number of households (P1538) and per capita income (P10622) or land area (P2046) -- geo-data-list= this is a comma-separated list of Q values, for a manually generated list that doesn't require nymbered parameters -- geo-scalefactor = can enlarge or shrink all the dots in proportion, depending an what the map needs -- geo-region = needs to be a Qvalue of the larger administrative region that the settlements/sub-regions are in -- geo-sub-region needs to be a Pvalue, but will default to P150 (some pages use 'has parts' = P527) -- geo-data3= etc numbered Qvalue item, if used in conjunction with other shape/label parameters -- shapeD= sets default shape, which migh be n-circle to include numbers. -- geo-number-size= will give a fixed size for shape numbers. (defaults to 12 when using proportional dots) -- geo-boundaries= if set to 1 will add any Qvalues for the sub-regions to 'map-data-light'. -- show-geo-dots= to use proportional dots. Will be fixed size if 0. (set mark-sizeD=0 to have no dot at all, eg to show labels or boundaries) -- find-geo-dots=1 or 0 (needs to also include geo-region) -- label-posD = gets additional options: [off] gives no label, [on] will position lower centre, -- [on+] includes numeric values beside/below label, [on^] will always put it below. [on-] always puts values beside if args['geo-data'..argval] then -- and (args['mark-title'..argval] or '') ~= 'none' then local pvalue = args['geo-data-type'] or 'P1082' local plabel = mw.wikibase.getLabel(pvalue) if not args['geo-number-size'] then args['geo-number-size']=12 end if not args['label-posD'] then args['label-posD'] = 'on' end qvalue=args['geo-data'..argval] -- total poulation is a Qvalue wikidata page entry, or add up all the geo-data values or use a random 10000 local region = args['geo-region'] -- or mw.wikibase.getEntityIdForCurrentPage() local thisPop=0 local areatxt='' local yearVal='' if mw.wikibase.getBestStatements(qvalue,pvalue)[1] then thisPop=tonumber(mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.amount) or 0 if pvalue == 'P2046' then -- area, so convert from ha to sq km if needed (/100) thisPop = convertToSqKm(thisPop, mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.unit) end if mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers and mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers.P585 then yearVal = mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers.P585[1].datavalue.value.time if yearVal then yearVal = yearVal:sub(2,5) end -- wikidata reports an entire date and time, so extract the year end else debugmsg(mw.addWarning(mw.wikibase.getLabel(qvalue)..' ('..qvalue..') has no recorded '..plabel)) end local scalefactor=args['geo-scalefactor'] or 1 if argval=='1' and args['mark-size1'] and not args['mark-sizeD'] then args['mark-sizeD'] = args['mark-size1'] end if argval=='1' and args['label-pos1'] and not args['label-posD'] then args['label-posD'] = args['label-pos1'] end if not args['mark-size'..argval] then if args['show-geo-dots'] == '0' then args['mark-size'..argval] = args['mark-sizeD'] or '1' -- just use a 'point' for the label to attach to else if thisPop>0 then args['mark-size'..argval] = round(math.sqrt(thisPop/geoTotalPop*100) * 8 * scalefactor,0) else args['mark-size'..argval] = args['mark-sizeD'] or '1' args['shape-outline'..argval]=args['shape-outline'..argval] or args['shape-outlineH'] end end end local posTemp=string.lower(multiCheck(args,'label-pos',argval,'on')) args['label-pos'..argval] = posTemp posTemp=splitItem(posTemp,2)[1] if pvalue == 'P2046' then thisPop = round(thisPop,2) areatxt=sqkm..areatxt -- round decimals if sq km area elseif pvalue == 'P10622' then if mw.wikibase.getBestStatements(qvalue,pvalue)[1] then local punit=(mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.unit or ''):match("Q.*") -- extract Q value if punit == 'Q4917' then areatxt=' US$' elseif punit == 'Q4916' then areatxt=' euro' elseif punit == 'Q25224' then areatxt=' £GBP' else areatxt=mw.wikibase.getLabel(punit)..areatxt end end end if posTemp=='off' then else if not args['label'..argval] then args['label'..argval] = mw.wikibase.getLabel(qvalue) end local tline=posTemp:find("[%+%^%-]") if tline then local tval=posTemp:sub(tline,tline) local eline posTemp=posTemp:sub(1,tline-1) if posTemp == 'left' or posTemp == 'right' or string.find(posTemp,'east') or string.find(posTemp,'west') then if tval == '^' then eline = '^' else eline = ' ' end -- only add line break if 'forced', used for left/right elseif tval == '+' or tval == '^' then eline = '^' -- add linebreak unless 'forced' not to else eline = ' ' -- must be a dash, so no line break end args['label'..argval] = args['label'..argval]..eline..'('..formatInt(thisPop)..')' args['label-pos'..argval] = posTemp..args['label-pos'..argval]:sub(tline+1,#args['label-pos'..argval]) end end if posTemp=='on' then if argval=='1' and (not args.ldyD) then args.ldyD = args['ldy'..argval] or '0' end if not args['ldy'..argval] then args['ldy'..argval] = args.ldyD end -- ensure ldy for that dot is added to the position args['label-pos'..argval]='center' local _,br = string.gsub(args['label'..argval],'%^','') local item=splitItem(args['label-size'..argval],2) args['ldy'..argval] = (args['ldy'..argval] or 0) + ((item[1] or 11) * ((br+1)*0.6)) + ((args['geo-number-size'] or 12) / 2) end local nt='' if args['mark-title'..argval] == 'nolist' then nt='nolist,' end if not args['mark-title'..argval] or args['mark-title'..argval] == '' or args['mark-title'..argval] == 'nolist' then if mw.wikibase.getSitelink(qvalue) then args['mark-title'..argval] = nt..'[['..mw.wikibase.getSitelink(qvalue)..']]' else args['mark-title'..argval] = nt..mw.wikibase.getLabel(qvalue) end end -- If mark-title is not set locally then attempt Sitelink value, or use label if thisPop == 0 then areatxt='' end captionTableText[argval]={} captionTableText[argval][1]= args['mark-title'..argval] captionTableText[argval][2]= formatInt(thisPop) if maplist.width >= 280 then captionTableText[argval][3]= yearVal else captionTableText[argval][3]='no-year' end if args['show-q-values'] == '1' then captionTableText[argval][4]='<small>[[wikidata:'..qvalue..'|'..qvalue..']]</small>' else local nbsp = '&nbsp;' if maplist.width < 230 then nbsp = ' ' end captionTableText[argval][4]='' if captionTableText[argval][3] == 'no-year' then captionTableText[argval][2] = captionTableText[argval][2]..'<small><sup>'..nbsp..'[[wikidata:'..qvalue..'|WD]]</sup></small>' else captionTableText[argval][3] = captionTableText[argval][3]..'<small><sup>'..nbsp..'[[wikidata:'..qvalue..'|WD]]</sup></small>' end end if args['mark-title'..argval] == 'none' then -- no title else args['mark-title'..argval] = args['mark-title'..argval]..' ('..plabel..' '..formatInt(thisPop)..areatxt..')' end if not args['mark-description'..argval] then args['mark-description'..argval] = (mw.wikibase.getDescription(qvalue) or '')..'<br>' end if (not args['mark-image'..argval]) and (mw.wikibase.getBestStatements(qvalue,'P18')[1]) then args['mark-image'..argval] = mw.wikibase.getBestStatements(qvalue,'P18')[1].mainsnak.datavalue.value end item = multiCheck(args,'shape',argval,'circle') --default to circle if geo-data if not string.find(item,'svgpath,') then item=string.lower(item) end -- ensure svgpath items retain original case values else item = multiCheck(args,'shape',argval,'image') if not string.find(item,'svgpath,') then item=string.lower(item) end -- ensure svgpath items retain original case values end if string.find(item,'n-',0,true)==1 or string.find(item,'l-',0,true)==1 then autoDotTag=string.sub(item,0,1) item=string.sub(item,3) end if item == 'image' then shapeResult[nval].shape = 'image:' shapeResult[nval].shapeFile =multiCheck(args,'mark',argval,'Red pog.svg') shapeWidth=-1 else shapeResult[nval].shape = item end item= multiCheck(args,'mark-size',argval,(shapeResult[nval].shapeSize or '14px')) -- add geo-data as fallback if already assigned local a,b,c= getsize(string.gsub(string.gsub(item,',','px')..'px','pxpx','px')) if b==a and args['mark-dim'..argval] then b= b / tonumber(string.match(args['mark-dim'..argval],"[%.%-?%d]+")) end shapeHeight=b/2 item=tostring(a)..'px'..tostring(b)..'px'..tostring(c)..'px' shapeResult[nval].shapeSize= item itemTab=splitItem(multiCheck(args,'shape-color',argval,'hard red'),2) shapeResult[nval].shapeColor=itemTab[1] or 'hardred' item=itemCheck(itemTab[2],'%') -- jump through the various opacity hoops and add to color if needed if not item then item=itemCheck(args['shape-opacity'..argval],'%') end if item and item~='0%' and item~='100%' then shapeResult[nval].shapeColor=shapeResult[nval].shapeColor..item end shapeResult[nval].shapeAngle=itemCheck(multiCheck(args,'shape-angle',argval,'0'),'deg') or '0deg' if args['shape-angle'..argval] then shapeResult[nval].shapeAngleArg=true else shapeResult[nval].shapeAngleArg=false end --sort out the outline entry itemTab=splitItem(multiCheck(args,'shape-outline',argval,'transparent,0,100,solid'),4) shapeResult[nval].outlineColor=itemTab[1] or 'dark grey' shapeResult[nval].outlineWidth=itemCheck(itemTab[2],'px') or '1px' if itemTab[3] and itemTab[3]~='100' and itemTab[3]~='0' then shapeResult[nval].outlineColor=shapeResult[nval].outlineColor..itemCheck(itemTab[3],'%') end shapeResult[nval].outlineStyle=itemTab[4] or 'solid' -- label size, background, outline shapeResult[nval].textAT=args['label-attribute'..argval] or '' itemTab=splitItem( multiCheck(args,'label-size',argval,'12'),3) shapeResult[nval].textSZ=itemCheck(itemTab[1],'px') or '12px' if itemTab[2]=='outline' then shapeResult[nval].textBG=itemTab[3] or 'transparent' shapeResult[nval].textOL='1px' elseif itemTab[3]=='outline' then shapeResult[nval].textBG=itemTab[2] or 'transparent' shapeResult[nval].textOL='1px' else shapeResult[nval].textOL='0px' shapeResult[nval].textBG=itemTab[2] or 'transparent' end if getColor(shapeResult[nval].textBG)==CTB['hardgrey'] and shapeResult[nval].textBG~='hardgrey' then shapeResult[nval].textBG= 'transparent' end --label color etc itemTab=splitItem(multiCheck(args,'label-color',argval, 'darkgrey','label-colour'),2) shapeResult[nval].textCL=itemTab[1] or 'darkgrey' if itemTab[2] and itemTab[2]~='0%' and itemTab[2]~='100%' then shapeResult[nval].textCL=shapeResult[nval].textCL..itemTab[2] end shapeResult[nval].textSP=itemCheck( multiCheck(args,'label-spacing',argval,'0'),'px') -- sets letter-spacing in px shapeResult[nval].textLH=itemCheck( multiCheck(args,'label-height',argval,'120'),'%') -- sets line height, 120% default shapeResult[nval].textNG=itemCheck(multiCheck(args,'label-angle',argval,'0'),'deg') if args['label-angle'..argval] then shapeResult[nval].textNGarg=true else shapeResult[nval].textNGarg=false end --sgd=dotTag attributes shapeResult[nval].tagSize=(args['geo-number-size'] or tostring(shapeHeight*1.5))..'px' local c1,c2=checkColors(shapeResult[nval].shapeColor) shapeResult[nval].tagColor=c2 shapeResult[nval].tagSpacer='0px' shapeResult[nval].tagAngle='0deg' -- sge extension line attributes local shapePos=splitItem(multiCheck(args,'label-pos',argval,'right'),6) if shapePos[2]=='with-line' or shapePos[2]=='n-line' then shapeResult[nval].textEW=(shapePos[3] or '1')..'px' -- width shapeResult[nval].textEC=(shapePos[4] or shapeResult[nval].shapeColor or 'darkgrey') elseif shapePos[2]=='photo-panel' then shapeResult[nval].textEW='2px' -- width shapeResult[nval].textEC=shapeResult[nval].textCL else shapeResult[nval].textEW='0px' -- width shapeResult[nval].textEC='grey'-- colour end shapeResult[nval].textES='solid' if argval=='H' then return dotResult end --Assign dot values local dotItem={} dotItem.group=nval dotItem.code=nval dotItem.posType=shapePos[2] or 'nil' if (shapePos[2] or '') =='photo-panel' then dotItem.ppwidth= tonumber(string.match((shapePos[4] or '110'),"%d+")) dotItem.ppheight= tonumber(string.match((shapePos[5] or '48'),"%d+") ) dotItem.photowidth=round(tonumber(string.match((shapePos[3] or '1.3'),"[%.%-?%d]+")) * dotItem.ppheight+1,0) dotItem.photoImage=args['mark-image'..argval] --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end if (shapePos[2] or '') =='mark-line' then local x=tonumber(argval or '1') dotItem.markDest=shapePos[6] or tostring(x-1) dotItem.mlWidth= tonumber(string.match((shapePos[3] or '1'),"%d+")) dotItem.mlStyle= shapePos[4] or 'solid' dotItem.mlGap=tonumber(string.match((shapePos[5] or '0'),"[%d]+")) shapeResult[nval].textEC=shapeResult[nval].outlineColor or 'darkgrey' end if args['mark-coord'..argval] then itemTab=splitItem(convertCoordsTrad (args['mark-coord'..argval]),2) if itemTab[1] == nil or itemTab[2] == nil then debugmsg(mw.addWarning('Unable to read coordinates for mark-coord'..argval)) else dotItem.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) or 0 end else dotItem.lat=tonumber(string.match(args['mark-lat'..argval],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(args['mark-lon'..argval],"[%.%-?%d]+")) or 0 end if args['dateline'..argval] and (args['dateline'..argval]=='1' or args['dateline'..argval]=='-1') then dotItem.lon=dotItem.lon+(tonumber(args['dateline'..argval] ) *360) end maplist.lon=dotItem.lon maplist.lat=dotItem.lat dotItem.gridx, dotItem.gridy = maptogrid(maplist,1) local item=args['mark-title'..argval] or '' -- sort out the caption, wikilink and plaintext tooltip items from dotLink if item=='none' then dotItem.param1='nomap nolist' item='' end if string.find(item,'nolist') then dotItem.param1='nolist,' item = string.sub(args['mark-title'..argval],8) or args['label'..argval] end dotItem.dotLink=item if item ~= '' then item=stripdivs(item) dotItem.title=delink({item}) local linkstart= string.find(item,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then dotItem.dlink=delink({string.sub(item,linkstart,string.find(item,']]',1,true)+1),wikilinks='target'}) -- debugmsg('dlink for '..item..' is '..dotItem.dlink) else dotItem.dlink='' end else dotItem.dlink='' dotItem.title='' end if autoDotTag=='n' then item=nval elseif autoDotTag=='l' then item=string.char(64+tonumber(nval)) else item='' end dotItem.dotTag = args['numbered'..argval] or item if shapePos[2]=='n-line' and (args['label'..argval] or args['label'..argval]=='') then if dotItem.dlink =='' then item=dotItem.dotTag..' '..args['label'..argval] else item='[['..dotItem.dlink..'|'..dotItem.dotTag..']] '..args['label'..argval] end else item=(args['label'..argval] or '') end if args['labela'..argval] then item = item..'^'..args['labela'..argval] end if args['labelb'..argval] then item = item..'^'..args['labelb'..argval] end local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end dotItem.labelText = a -- convert hats to line breaks if argval=='' then item = (args['label-offset-x']) or (args.ldx) or '0' else item=args['label-offset-x'..argval] or args['ldx'..argval] or args['label-offset-xD'] or args.ldxD or args['label-offset-x1'] or args.ldx1 or '0' end dotItem.dx=tonumber(string.match(item,"[%.%-?%d]+")) if argval=='' then item = (args['label-offset-y']) or (args.ldy) or '0' else item=args['label-offset-y'..argval] or args['ldy'..argval] or args['label-offset-yD'] or args.ldyD or args['label-offset-y1'] or args.ldy1 or '0' end dotItem.dy=tonumber(string.match(item,"[%.%-?%d]+")) dotItem.labelPos=shapePos[1] if args['mark-image'..argval] then dotItem.imageName= args['mark-image'..argval] end dotItem.info=args['mark-description'..argval] or '' table.insert(dotResult,1,dotItem) -- add to start of list, so they are in reverese order for displaying -- debugmsg(dotItem.code..' = '..dotItem.lat..','..dotItem.lon..': '..dotItem.labelText..' - '..dotItem.title..', '..shapeResult[nval].shapeSize) return dotResult end local function tradstyleParseShapes(args,dotTable,dotmax) local sgNumbers,sgSortable={},{} for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['geo-data'..x] and (not args['mark-coord'..x]) and (not args['mark-lat'..x]) then -- get lat and lon values from wikidata and put them into the args.lat and args.lon for use wherever needed if (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1]) then args['mark-lat'..x] = (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1].mainsnak.datavalue.value.latitude) or '0' args['mark-lon'..x] = (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1].mainsnak.datavalue.value.longitude) or '0' local p=args['geo-data-type'] or 'P1082' if mw.wikibase.getBestStatements(args['geo-data'..x],p)[1] then local n = tonumber(mw.wikibase.getBestStatements(args['geo-data'..x],p)[1].mainsnak.datavalue.value.amount) or 0 if p == 'P2046' then -- area, so convert to sq km if needed (/100) n = convertToSqKm( n, mw.wikibase.getBestStatements(args['geo-data'..x],p)[1].mainsnak.datavalue.value.unit) end geoTotalPop = geoTotalPop + n end else debugmsg(mw.addWarning('could not find coords for '..mw.wikibase.getLabel(args['geo-data'..x])..', '..args['geo-data'..x])) end end if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) then sgNumbers[x]=x -- add it to the list end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form (needs a second table, seemingly) if args['mark-coord'] or (args['mark-lat'] and args['mark-lon']) then table.insert(sgSortable,'0') end -- add it to the end of the list local default={} default.D={} local dotResult={} for k,sgnum in pairs(sgSortable) do -- work through the sorted list, parsing each set of shapes in turn, from 1 upwards shapeList[sgnum]={} dotTable=assignTradstyleShape(shapeList,default,dotTable,args,sgnum) end dotTable=assignTradstyleShape(shapeList,default,dotTable,args,'H') -- construct an extra highlight shapeitem local unitConvert=1 local areaUnits='' if args['geo-region'] and (args['find-geo-dots'] or '1') ~= '0' then local region = args['geo-region'] -- or mw.wikibase.getEntityIdForCurrentPage() local pvalue = args['geo-data-type'] or 'P1082' local plabel = mw.wikibase.getLabel(pvalue) local subhead='' -- Assemble the subhead content, a line of text about the region as a whole if region and mw.wikibase.getBestStatements(region,pvalue)[1] then local punit=mw.wikibase.getBestStatements(region,pvalue)[1].mainsnak.datavalue.value.unit:match("Q.*") -- extract Q value if pvalue == 'P10622' then -- add other currency-based pvalues here areaUnits=mw.wikibase.getLabel(punit) end if punit == 'Q35852' then unitConvert=100 areaUnits = ' '..sqkm -- if its hectares, convert to sq km elseif punit == 'Q232291' then unitConvert = (1/2.59) areaUnits = ' '..sqkm -- or this if it's square miles elseif punit == 'Q81292' then unitConvert = 247.1 areaUnits = ' '..sqkm -- or this if it's acres elseif punit == 'Q712226' then unitConvert = 1 areaUnits = ' '..sqkm -- or this if it's already sq km elseif punit == 'Q4917' then areaUnits=' US$' -- replace long version with some shortform currencies () elseif punit == 'Q4916' then areaUnits=' euro' elseif punit == 'Q25224' then areaUnits=' £GBP' end -- if pvalue == 'P2046' then -- may need to add other instances of sq km so add in other P-items here if they need units showing -- end geoTotalPop=(mw.wikibase.getBestStatements(region,pvalue)[1].mainsnak.datavalue.value.amount) / unitConvert local yearVal if mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers then yearVal = mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P585[1].datavalue.value.time if yearVal and yearVal:sub(1,1) == '+' then yearVal = yearVal:sub(2,5) if mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459 and mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459[1] then local methodVal = mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459[1].datavalue.value.id if methodVal then yearVal=yearVal..' '..mw.wikibase.getLabel(methodVal) end end yearVal = ' ('..yearVal..')' else yearVal ='' end end subhead=totalItems..' '..mw.wikibase.getLabel(region)..' '..plabel..' ([[wikidata:'..region..']]) = '..formatInt(round(geoTotalPop),1)..areaUnits..yearVal else if pvalue == 'P2046' then areaUnits = sqkm end subhead=totalItems..' '..mw.wikibase.getLabel(region)..' '..plabel..' ('..sumOfDatapoints..') = '..formatInt(round(geoTotalPop),1)..areaUnits end if maplist.width < 350 and #plabel > 4 then plabel=string.sub((plabel or ' '),1,3)..'.' end if maplist.width < 330 then numberCol = ' ' end if maplist.width < 280 then year = 'no-year' end captionTable = subhead if (args['show-q-values'] or '') ~= '1' then captionTable = captionTable..'<div style="float:right;"><small> (WD&nbsp;=&nbsp;'..wikidataLink..')</small></div><br>' wikidataLink='' end captionTable = captionTable..'<table class="wikitable plainrowheaders sortable mw-collapsible mw-collapsed" style="width: 100%;"><caption>'..(args.toggletext or toggletext)..' </caption>' captionTable = htmlTableLine(captionTable,numberCol,mw.wikibase.getLabel(region),plabel..' '..areaUnits, year, wikidataLink,'header') end return dotTable end local function checkfortooltip (title,dx,dy,dotlabel,dlink,nolabel) -- returns tlink if available, and dlink, if needed and tshape=true if shape needed local tshape,tlink = false,"" if dlink~='' and not nolabel then tlink=dlink end if (tlink=="" or nolabel) and title~="" then tshape=true end -- tshape flags True if title is wanted for shape if (not (dx==0 or dy==0) or dotlabel=='') and title~='' then tshape=true end -- add tooltip to shape if its number has moved return tshape,tlink end local function tshift(angle) -- adjustment to place text near the centre of a triangle, shifted to allow rotation of triangle shape local x=tonumber(string.match(angle,"%-?%d+")) if x<0 then x=360+x end -- set to a single degree direction, 0 to 360 if x>359 then return 0,0 end -- shift the centre of the triangle based on rotation value if x <=40 or x>=320 then return -0.17,0 -- triangle up= -shiftv elseif x>=140 and x<=220 then return 0.17,0 --triangle down= +shiftv elseif x >220 then return 0,-0.17 --triangle left= -shifth elseif x >40 then return 0,0.22 --triangle right= +shifth end return 0,0 end local function makeTriangle(result,row,shape,outline,tlink) local w,h,r=getsize(shape.shapeSize) if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) w=w+p*2 h=h+p*2 end table.insert(result,'<div ') if tlink then table.insert(result,' title="'..row.title..'" ') end table.insert(result,'style="display:inline-block; position: absolute') if shape.shapeAngle ~= '0deg' then table.insert(result,'; transform: rotate('..shape.shapeAngle..')') end local shiftv,shifth=0,0 shiftv,shifth=tshift(shape.shapeAngle) table.insert(result,'; top: '..tostring(row.gridy-h/2+h*shiftv)..'px') table.insert(result,'; left: '..tostring(row.gridx-w/2+w*shifth)..'px; width: 0; height: 0; outline-width: 0px') table.insert(result,'; border-left: '..tostring(w/2)..'px solid transparent') table.insert(result,'; border-right: '..tostring(w/2)..'px solid transparent') if outline then -- fill with outline colour, to make a 'base layer' or shape colour table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.outlineColor).. '">') else table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.shapeColor).. '">') end table.insert(result,'</div>') end local function makeSquare(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) if shape.shape=='square' then h=w end -- squares are always square!. box can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end if r~=0 then div:css('border-radius',tostring(r).."px") end if shape.shape=='panel' then div:css('top', tostring(row.gridy).."px") div:css('left', tostring(row.gridx).."px") else div:css('top', tostring(row.gridy-h/2).."px") div:css('left', tostring(row.gridx-w/2).."px") end div:css('width', tostring(w).."px") div:css('height', tostring(h).."px") div:css('background-color', getColor(shape.shapeColor) ) div:css('color', 'inherit') table.insert(result,tostring(div)) end local function makeCircle(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding if shape.shape=='circle' then h=w end -- circles are always round. ellipse can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end div :css('top', tostring(row.gridy-h/2).."px") :css('left', tostring(row.gridx-w/2).."px") :css('width', tostring(w).."px") :css('height', tostring(h).."px") :css('border-radius', "50%") :css('background-color', getColor(shape.shapeColor) ) :css('color', 'inherit') table.insert(result,tostring(div)) end local function makeRuleA(result,row,shape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local lineV=0 if shape.shape=='rulea' then lineV=oWid*3+16 end table.insert(result,"<div style=\"display:inline-block; position: absolute") -- create a square transparent container, which will rotate line and arrow together table.insert(result,"; top:"..tostring(row.gridy - w/2).."px") table.insert(result,"; left:"..tostring(row.gridx - w/2).."px") table.insert(result,"; width:"..tostring(w).."px") table.insert(result,"; height:"..tostring(w).."px; background:transparent; color:inherit;") table.insert(result,"; transform: rotate( "..tostring(tonumber(string.match(shape.shapeAngle,"[%.%-?%d]+")) - 90).."deg);\">" ) table.insert(result,"<div style=\"display:inline-block; position: absolute") -- put the line (as a border-right) across the container table.insert(result,"; top:"..tostring(lineV).."px") table.insert(result,"; left:"..tostring((w - oWid )/2).."px; width: 0px") table.insert(result,"; height: "..tostring(w -lineV).."px") table.insert(result,"; border-right: "..shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) table.insert(result,"; background:transparent; color:inherit;\"></div>") if shape.shape=='rulea' then table.insert(result,"<div style=\"display:inline-block; position: absolute; top: 0px") --and add arrow head table.insert(result,"; left:"..tostring(w/2-( oWid/2)-oWid*0.55-2).."px; width: 0; height: 0; outline-width: 0px") table.insert(result,"; border-left: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-right: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-bottom: "..tostring(oWid*3+16).."px solid "..getColor(shape.outlineColor).."\"></div>") end table.insert(result,"</div>") end local function makeCurveA(result,row,shape) -- draw a curve with Arrow ----- local w,h=getsize(shape.shapeSize) -- = width,height local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local Angle=tonumber(string.match(shape.shapeAngle,"[%.%d]+")) table.insert(result,'<div style="position: absolute;') --set up out div, which will allow the whole to rotate table.insert(result,'top:'..tostring(row.gridy - (w + oWid*3+16)/2)..'px;') table.insert(result,'left:'..tostring(row.gridx - ( w + oWid*3+16)/2)..'px; ') table.insert(result,'width: '..tostring(w+oWid*3+16)..'px; ') table.insert(result,'height: '..tostring(w+oWid*3+16)..'px; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate( '..tostring(Angle-120)..'deg);">') else table.insert(result,'transform: rotate( '..tostring(Angle -62)..'deg);">') end table.insert(result,'<div style="position: absolute;') --set up div for the rounded corner of a rectangle table.insert(result,'border-left: '..shape.outlineWidth..' '..shape.outlineStyle..' '..getColor(shape.outlineColor)..';') if shape.shape=='curvea' then table.insert(result,'border-radius: 10000px 0 0 '..tostring(w)..'px; top:0px; left:'..tostring(w*0.25)..'px;') else table.insert(result,'border-radius: '..tostring(w)..'px 0 0 10000px;') table.insert(result,'top:'..tostring((oWid*3+16)/2+w*0.15)..'px; left:'..tostring(w*0.25)..'px;') end -- and add a triangular arrow head table.insert(result,'width: '..tostring(w)..'px; height: '..tostring(w)..'px;"></div><div style="position: absolute; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate(180deg); top:'..tostring(w-1)..'px; ') else table.insert(result,'transform: rotate(0deg); top: '..tostring(0-( ( oWid*3+16)/2)+1+( w*0.15) )..'px;') end -- reverse table.insert(result,'left:'..tostring(0-( oWid*0.6)-2+(w*0.25))..'px;') table.insert(result,'width: 0; height: 0; outline-width: 0px; border-left: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-right: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-bottom: '..tostring(oWid*3+16)..'px solid '..getColor(shape.outlineColor)..';"></div></div>') end local function makeLineTo (result,x1,y1,x2,y2,oWid, oStyle, oCol,double,shape) table.insert(result,"<div style=\"display:inline-block; position: absolute;") -- draw a line between x1,y1 and x2,y2, px-coords where 0,0 is centre of frame -- Maths calculations thanks to ES local tw if double <= 1 then tw = tonumber(string.match(oWid,"[%.%d]+"))/2 -- shift centre by half the line width else tw = tonumber(string.match(oWid,"[%.%d]+")) + (double/2) - 0.5 -- shift centre by a line width + half the gap end -- debugmsg(oWid..'+'..double..'/2='..tw) table.insert(result,"left: "..tostring(x1+( (x2-x1)/2) - (math.sqrt( ( x2-x1)^2 + (y2-y1)^2 )/2) ).."px;") table.insert(result,"top: "..tostring(y1+( ( y2-y1 )/2) - tw).."px;") table.insert(result,"width: "..tostring(math.sqrt( (x2-x1 )^2 + ( y2-y1 )^2) ).."px;") table.insert(result,"height: "..tostring(double).."px; background-color:transparent; color:inherit; ") table.insert(result,"outline-width: 0; border-bottom: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) if double>1 then table.insert(result,"border-top: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) end local lineAngle=math.atan(( y2-y1)/( x2-x1 ) )*180/math.pi -- if x1 == x2 then lineAngle=90 end table.insert(result,"transform: rotate("..lineAngle.."deg);\"></div>") -- debugmsg('text angle started as '..shape.textNG..' and lineAngle = '..lineAngle) if (lineAngle < -90) or (lineAngle >90) then lineAngle=lineAngle-180 end if shape and not shape.textNGarg then shape.textNG=tostring(round(lineAngle,0))..'deg' end -- Put in line angle for a default text angle as well -- if (y2 >= y1) and (x2 <= x1) then lineAngle=lineAngle-180 debugmsg('arrow angle corrected') end if x2 < x1 then lineAngle=lineAngle-180 end if shape and not shape.shapeAngleArg and shape.shape == 'arrowhead' then shape.shapeAngle=tostring(round(lineAngle,0))..'deg' end -- line angle for arrowhead -- debugmsg('text angle is now '..shape.textNG..'with arrow at '..lineAngle) end local function makeClipPath(result,row,shape,outline,tshape) --tshape is a flag to show if the tooltip (title=) is wanted -- return the text css div code to position and draw a shape occupying a specified clippath if string.find(shape.shape, 'svgpath,') and #shape.shape > 9 then pathshape.svgpath=string.sub(shape.shape,9) shape.shape='svgpath' end if not pathshape[shape.shape] then debugmsg(mw.addWarning('shape'..row.code..' = '..shape.shape..'. The shape name is not defined')) return end local w,h,r=getsize(shape.shapeSize) if shape.shape == 'arrowhead' then -- enlarge arrowheads, as they only go to the middle of the notional shape-size w=w*2.5 h=h*2.5 end if string.match(shape.shape,"circle") or string.match(shape.shape,"square") then h=w end -- use ellipse and box for stretched shapes local shifth,shiftv = 0,0 if string.match(shape.shape,"triangle") then shiftv,shifth =tshift(shape.shapeAngle) end if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) or 0 w=w+p*2 h=h+p*2 end table.insert(result,"<div ") if tshape then -- Add tooltip if needed table.insert(result," title=\""..row.title.."\" ") end table.insert(result,"style=\"display:inline-block; position: absolute; background-color:") if outline then table.insert(result,getColor(shape.outlineColor)) -- fill with outline colour, to make a 'base layer' else table.insert(result,getColor(shape.shapeColor)) end table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;"..pathshape[shape.shape].."&#39;) ") -- adds the required clippath data from the table of pathshape string literals table.insert(result,"; top:"..tostring(row.gridy - 10 + h*shiftv).."px") table.insert(result,"; left:"..tostring(row.gridx - 10 + w*shifth).."px") table.insert(result,"; width:20px") -- needs to be a path within a 20px20px box, and then rescales using size values to match other shape sizes table.insert(result,"; height:20px; transform:") if shape.shapeAngle ~= "0deg" then table.insert(result," rotate("..shape.shapeAngle..")") end table.insert(result," scale("..tostring(w/16)..", "..tostring(h/16)..")") table.insert(result,"\"></div>") end local function makeImage(result,row,shape) local w,h,r=getsize(shape.shapeSize) local image=shape.shapeFile if not image or image=='' then image='Red pog.svg' end local imagediv=mw.html.create ('div') imagediv:css('position', "absolute") if shape.shapeAngle ~= "0deg" then imagediv:css('transform',"rotate("..shape.shapeAngle..")") end imagediv :css('top', (row.gridy+2 + math.min(h/2-12,0) - h/2).."px") --File seems to adjust pos for small images :css('left', (row.gridx-1-w/2).."px") :css('background-color', "transparent" ) :css('color','inherit') :wikitext('[[file:'..image..'|'..tostring(w+2)..'px|alt='..(row.title or '')..'|link=]]') table.insert(result,tostring(imagediv)) end local function makePhotoPanel(result,row,shape) local h=row.ppheight table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;' ) table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') table.insert(result, 'width: '..tostring(row.ppwidth)..'px; height: '..tostring(h)..'px; border-radius: 2px; color:inherit;') table.insert(result, 'background-color: #E8E8D6; outline: 2px solid '..getColor(shape.textCL)..'; box-shadow: 2px 2px 4px #33203335;"></div>') if row.photoImage and row.photowidth >0 then table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;') if row.labelPos=='left' or string.find(row.labelPos,'west') then table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') row.dx=row.dx+row.photowidth/2 else table.insert(result, 'left: '..tostring(row.gridx+row.dx + (row.ppwidth-row.photowidth) - row.ppwidth/2)..'px;') row.dx=row.dx-row.photowidth/2 end table.insert(result, 'background-color:transparent; color:inherit; border-radius: 2px;">') table.insert(result,'[[File:'..row.photoImage..'|x'..tostring(h)..'px|File:'..row.photoImage..']]</div>') row.labelPos='center' end end local function makePanelText(result, row, shape) local w,h,r=getsize(shape.shapeSize) local ty=tonumber(string.match(shape.textSZ or '11',"%d+") ) table.insert(result,'<div style="position:absolute; line-height: 120%; font-size: '..shape.textSZ..'; color:'..getColor(shape.textCL)..';') if row.labelPos == 'left' or string.find(row.labelPos,'west') then table.insert(result,'top: '..tostring(row.dy+row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+3)..'px; text-align: left; width:'..tostring(w)..'px;') elseif row.labelPos == 'right' or string.find(row.labelPos,'east') then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+w-3)..'px; text-align: right; width: max-content; transform: translateX(-100%);') elseif row.labelPos == 'top' or row.labelPos== 'north' then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') elseif row.labelPos == 'bottom' or row.labelPos=='south' then local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*1.1 table.insert(result,'top: '..tostring(row.gridy+w-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') else -- center or centre local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*0.6 table.insert(result,'top: '..tostring(row.gridy+(h/2)-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') end if shape.textSP and shape.textSP ~='0px' then table.insert(result,"letter-spacing: "..shape.textSP..';') end if shape.textLH and shape.textLH ~='120%' then table.insert(result,"line-height: "..shape.textLH..';') end table.insert(result,"vertical-align: middle;\">"..row.labelText.."</div>") end local function makeTextItem(result, row, shape, align, tlink, textItem, dotItem) local w,h,r=getsize(shape.shapeSize) table.insert(result,"<div ") if row.title ~= "" then table.insert(result," title=\""..row.title.."\" ") end local ty,bry,linkoffset = 0,0,0 local compy=0 local lh=tonumber(string.match(shape.textLH or '120',"%d+"))/100 if dotItem==1 or (dotItem==2 and row.posType~='n-line') then -- if there is a dotTag in the middle of the shape then use the tag settings ty=tonumber(string.match(shape.tagSize,"%d+")) or 0 table.insert(result,"style=\"position:absolute; line-height: 120%; top: "..tostring(row.gridy-ty*0.62)) table.insert(result,"px; left: "..tostring(row.gridx).."px; width: fit-content; ") table.insert(result,"text-align: center; color: "..getColor(shape.tagColor).."; background-color: transparent;") local trf="" if shape.tagAngle ~="0deg" then trf=" rotate("..shape.tagAngle..")" end table.insert(result, "transform: translateX(-50%)"..trf.."; font-size: "..shape.tagSize..";") if shape.tagSpacer~='0px' then table.insert(result, "letter-spacing:"..shape.tagSpacer..";") end else -- or add tfx settings for left, right or center align, colors, backgrounds, border-outline table.insert(result,'style="position:absolute; ') if dotItem==2 then -- dotTag is out at x,y so 85% ty=tonumber(string.match(shape.tagSize or '0',"%d+")) table.insert(result,'font-size: '..shape.tagSize..'; padding:0px 2px;line-height: 85%; top: '..tostring(row.dy+row.gridy-ty*0.52)) else -- it is labelText, so use textLH or 120% ty=tonumber(string.match(shape.textSZ or '11',"%d+")) if row.labelPos=='northwest' or row.labelPos=='northeast' then compy=-ty elseif row.labelPos=='southeast' or row.labelPos=='southwest' then compy=ty/2 end if row.labelPos and not (row.labelPos== 'auto' or row.labelPos=='') then bry=(select(2, string.gsub(row.labelText,"<br>", ""))*lh) -- is it a multiline text? expand by line-height /120%? if row.posType == 'with-line' then bry=0 end if row.labelPos=='bottom' or row.labelPos == 'south' then bry = 0 -- and shift by none, all or half elseif row.labelPos=='top' or row.labelPos == 'north' then if shape.shape=='image:' then bry= 1 + math.min(w/2-10,0)-bry*ty else bry= -bry*ty+2 end else bry=-bry*(ty/2 * lh) end end if row.posType == 'photo-panel' then bry=bry+3 end table.insert(result,'font-size: '..shape.textSZ..'; padding:0px 3px;line-height: '..shape.textLH..'; ') table.insert(result,'top: '..tostring(row.dy+row.gridy+compy+bry-ty*lh/2)) end table.insert(result,"px; left: "..tostring(row.dx+row.gridx).."px; color: "..getColor(shape.textCL).."; ") table.insert(result,"width: max-content; ") local trf="" if shape.textNG ~="0deg" then trf="rotate("..shape.textNG..")" end if shape.textOL~="0px" then table.insert(result,"background-color: "..getColor(shape.textBG).."; ") table.insert(result,"border: "..shape.textOL.." solid "..getColor(shape.textCL).."; border-radius:6px;") else table.insert(result,"background-color: transparent;") end if row.labelPos=="right" or string.find(row.labelPos,'east') then table.insert(result,"text-align: left; ") linkoffset=w if shape.textNG ~="0px" then table.insert(result,"transform-origin: left; transform: rotate("..shape.textNG.."); ") end elseif row.labelPos=="left" or string.find(row.labelPos,'west') then if shape.textNG ~="0px" then table.insert(result,"transform-origin: right;") end linkoffset=-w table.insert(result,"text-align: right; transform: translateX(-100%) "..trf.."; ") else table.insert(result,"text-align: center; transform: translateX(-50%) "..trf.."; ") end table.insert(result,'font-weight: normal; line-height: '..shape.textLH..'; letter-spacing:'..shape.textSP..'; vertical-align: bottom;') end if string.find(shape.textAT or '','bold') then textItem='<b>'..textItem..'</b>' end if string.find(shape.textAT or '','italic') then textItem='<i>'..textItem..'</i>' end if shape.textOL=='0px' and shape.textBG~='transparent' and dotItem==0 then table.insert(result,'\"><span style=\"background-color: '..getColor(shape.textBG)..'; color:inherit;\">'..textItem..'</span></div>') else table.insert(result,"\">"..textItem.."</div>") end if tlink~='' and not string.match(row.param1 or "","nolink") then table.insert(result,makeLinkBox(row.gridx+row.dx+linkoffset, row.gridy+row.dy+bry, 16, row.dotTag..' '..row.title,tlink)) end end local function getshapetable(row,shape) -- Construct CSS divs for a dot from shape and map data local result={} local w,h,rndg=getsize(shape.shapeSize) local tshape,tlink=checkfortooltip(row.title,row.dx,row.dy,row.dotTag,row.dlink,string.match(row.param1 or "","nolink") ) local align=row.labelPos or '' local offsetx,offsety=0,0 local ty=tonumber(string.match((shape.tagSize or 9),"%d+" )) if row.labelText and row.labelText~='' then ty=tonumber(string.match((shape.textSZ or 11),"%d+" )) else align='center' end -- it is just for dotTag, so justify center -- identify align value and extend offsets local widthzone,heightzone = (w/2)+1,(h/2)+1 local theta,r = math.deg( math.atan2(row.dy, row.dx)) , math.sqrt(row.dx^2 + row.dy^2) if align=='auto' or align=='' then if (theta < -112 or theta > 112) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "left" offsetx=1 elseif (theta > -68 and theta < 68) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "right" offsetx=-1 elseif theta <0 then offsety=ty/2-1 -- bottom else offsety=0-ty/2+1 -- top end elseif align=='left' or string.find(align,'west') then row.dx=row.dx - w/2 widthzone=4 offsetx=-1 elseif align=='right' or string.find(align,'east') then row.dx=row.dx + w/2 widthzone=4 offsetx=1 elseif align=='top' or align=='north' then if string.find(shape.shape,'curve') then row.dy=row.dy-(h/2)-12 heightzone=4 offsety=ty/2-1 else row.dy=row.dy - h-2 heightzone=4 offsety=ty/2-1 end elseif align=='bottom' or align=='south' then if string.find(shape.shape,'curve') then row.dy=row.dy+(h/2) heightzone=4 offsety=ty/2-1 else row.dy=row.dy + h+2 heightzone=4 offsety=0-ty/2-1 end end if shape.textEW ~= "0px" and not string.match(row.param1 or "","noline") then -- debugmsg('r-(w/2) > widthzone r-w/2='..tostring(r-(w/2))..', widthzone='..widthzone..', label='..(row.labelText or '')..', postype='..(row.posType or '')) makeLineTo(result, row.gridx+1, row.gridy-1, row.gridx+row.dx+offsetx, row.gridy+row.dy+offsety, shape.textEW, shape.textES,shape.textEC,1) -- debugmsg('n-line for '..row.code..':'..tostring(row.gridx+1)..', '..tostring(row.gridy-1)..', '..tostring(row.gridx+row.dx+offsetx)..', '..tostring(row.gridy+row.dy+offsety)..', with width '..tostring(shape.textEW)..'px '..(shape.textES or 'no style')..' and color '..shape.textEC) end if row.posType and row.posType=='mark-line' then if row.gridx2 and row.gridy2 then makeLineTo(result, row.gridx, row.gridy, row.gridx2, row.gridy2, tostring(row.mlWidth)..'px', row.mlStyle, getColor(shape.textEC),row.mlGap,shape) -- debugmsg('mark-line line drawn from '..row.code..' with width '..tostring(row.mlWidth)..'px '..(row.mlStyle or 'no style')..' and color '..getColor(shape.textEC)) end end if w ~= 0 then if shape.shape=='itriangle' then shape.shape='triangle' shape.shapeSize=tostring(w)..'px'..tostring(w/2)..'px' end if shape.shape=="triangle" then if shape.outlineWidth ~= "0px" then makeTriangle(result,row,shape,true,false) -- larger triangle to give the outline, if required end makeTriangle(result,row,shape,false,tshape) -- smaller triangle to fit over the top elseif shape.shape=="square" or shape.shape=="box" or shape.shape=='panel' then makeSquare(result,row,shape,tshape) elseif shape.shape=="circle" or shape.shape=="ellipse" then makeCircle(result,row,shape,tshape) elseif string.find(shape.shape,'image:')==1 then makeImage(result,row,shape) elseif shape.shape=="rulea" or shape.shape=='rule' then makeRuleA(result,row,shape) elseif shape.shape=="curvea" or shape.shape=="curvec" then makeCurveA(result,row,shape) else -- use a pathshape clipPath if shape.outlineWidth ~= "0px" then makeClipPath(result,row,shape,true,false) -- larger path-shape to give the outline, if required end makeClipPath(result,row,shape,false,tshape) end end if row.ppwidth and row.ppwidth>0 then makePhotoPanel(result,row,shape) end if shape.shape=='panel' and row.labelText then makePanelText(result, row, shape) else if row.dotTag and row.dotTag ~= "" then -- there is a dotTag if (row.dx==0 and row.dy==0) and w>0 then -- it is on the dot so if dotsize is not 0 any label is ignored makeTextItem(result, row, shape, align, tlink, '<b>'..row.dotTag..'</b>', 1) else if row.labelText and row.labelText~='' then -- tag and label both used makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.labelText, 0) else makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.dotTag, 2) -- tag is ouside the dot end end else if (row.labelText and row.labelText~='') then -- just the label. No tag makeTextItem(result, row, shape, align, tlink, row.labelText, 0) end end end if tlink and tlink~='' then table.insert(result,makeLinkBox(row.gridx, row.gridy,w+3,row.dotTag..' '..row.title,tlink)) end return table.concat(result) end local function getmapframecontent(args,use) local result, comma= {}, '' local propertyTable= {} local color=splitItem(string.lower( args['map-data-color'] or 'dark orange'), 2) if use == 'basemap' then table.insert(result, '[') propertyTable.stroke = "#000000" propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') if color[2] then propertyTable['stroke-opacity'] = tonumber(color[2] or '20')/100 end else propertyTable.title = args['map-data-text'] or args['map-data'] or '' propertyTable.stroke = getColor(color[1]) propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') propertyTable['stroke-opacity'] = tonumber(color[2] or '100')/100 end if args['map-data-inverse'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geomask', ids = args['map-data-inverse'], properties = {title = (args['map-data-text'] or ''), stroke = '#555555', fill = '#555555', ['fill-opacity'] = 0.1, ['stroke-width'] = 1, ['stroke-opacity'] = 0.5 } } table.insert(result, mapJson) comma=', ' end if args['map-data-heavy'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-heavy'], properties = { title = args['map-data-heavy'], stroke = propertyTable.stroke, ['stroke-width'] = 9, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.2 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data-light'] and args['map-data-light'] ~= '' then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-light'], properties = { title = args['map-data-light'], stroke = propertyTable.stroke, ['stroke-width'] = 3, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.4 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data'] then propertyTable['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.2 local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data'], properties = propertyTable } table.insert(result, comma .. mapJson) comma=', ' end if args['map-wdqs'] then -- inserts a SPARQL wdqs query to find geopoints, geoshapes etc from wikidata local mapSparql = mw.text.jsonEncode{ type = 'ExternalData', service = args['map-wdqs-type'] or 'geopoint', query = args['map-wdqs'], } table.insert(result, comma .. mapSparql) -- debugmsg('mapSparql = ' .. mapSparql) comma=', ' end if args['map-raw'] then table.insert(result, comma .. args['map-raw']) end if use=='basemap' then table.insert(result,']') end return table.concat(result) end --eg | minilocator=filename,bottom right,132px153px, 38%,60%, 22px local function makeLocatorMap (args, result) local miniFile,pos,itemlist,miniW,miniH, miniX,miniY,miniBox, miniBH if args['mini-locator'] then pos,miniFile=extractItem(args['mini-locator']) -- first item is filename, in quotes if it includes commas itemlist=splitItem(pos,5) -- put items in a table filename removed, position,WpxHpx, x%y%,box pos=itemlist[2] or 'right' miniW,miniH=getsize(itemlist[3]) miniX=tonumber(string.match(itemlist[4] or '0','[%d]+'))*miniW/100 miniY=tonumber(string.match(itemlist[5] or '0','[%d]+'))*miniH/100 miniBox=tonumber(string.match(itemlist[6] or '0','[%d]+')) miniBox=miniBox*miniW/100 miniBH=miniBox * maplist.height/maplist.width elseif args['mini-file'] then miniFile = args['mini-file'] pos=string.lower(args.minimap or 'right') if pos=='file' then pos='right' end miniW,miniH = tonumber(args['mini-width'] or 60), tonumber(args['mini-height'] or 60) -- find top left corner of locator miniBox,miniBH=tonumber(args['minimap-boxwidth'] or '0'),0 -- firm up pos offsets for dot (with % or not) and boxsize if any, miniX, miniY=args['minipog-gx'],args['minipog-gy'] if not miniX then miniX=tonumber(args['minipog-x'] or '0') else miniX=tonumber(miniX)*tonumber(miniW)/100 end if not miniY then miniY=tonumber(args['minipog-y'] or '0') else miniY=tonumber(miniY)*tonumber(miniH)/100 end if args['minipog-gx'] then miniBox=miniBox*miniW/100 end miniBH=miniBox * maplist.height/maplist.width else return end local miniTop,miniLeft=0,1 if not string.find(pos,'top') then --only use top left, as link box is in top right or put bottom left or right miniTop=maplist.height+2-miniH if string.find(pos,'right') then miniTop=miniTop-15 -- to avoid (c) line miniLeft=maplist.width-1-miniW end end table.insert(result,'<div style="position: absolute; outline-width: 1px; outline-style: solid; outline-color: white;') table.insert(result,'top: '..tostring(miniTop)..'px; left:'..tostring(miniLeft)..'px; width:'..tostring(miniW)..'px;' ) table.insert(result,'background-color:transparent; color:inherit;">[[File:'..miniFile..'|'..tostring(miniW)..'px|File:'..miniFile..']]</div>') if miniX and miniX>0 then if args['minipog-y'] then miniY=miniY/1.04 end if args['minipog-x'] then miniX=miniX/1.04 end if miniBox<1 then table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-3-6)..'px; left:'..tostring(miniX+miniLeft-3)..'px;') table.insert(result, 'width: 6px; background-color:transparent; color:inherit;">[[File:Red pog.svg|6px|link=]]</div>') else table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-miniBH/2)..'px; left:'..tostring(miniX+miniLeft-miniBox/2)..'px;') table.insert(result, 'width: '..tostring(miniBox)..'px; height:'..tostring(miniBH)..'px; outline:1px solid #AA1205; background-color:#AA120522; color:inherit;"></div>') end end end local function makeArcText(args,result,nval) local items, itemlist='',{} local arcText='' if args['arc'..nval] then items=convertCoords (args['arc'..nval]) items,arcText=extractItem(items) -- first item is text, in quotes if it includes commas itemlist=splitItem(items,9) -- put items in a table: text, lat,lon,size,color,angle,gap,radius,ellipse if itemlist[4]=='' then itemlist[4]='12' end itemlist[4]=string.match((itemlist[4] or '12'),"[%.%-?%d]+") if itemlist[5]=='' then itemlist[5]='grey' end itemlist[6]=string.match((itemlist[6] or '0'),"[%.%-?%d]+") itemlist[7]=string.match((itemlist[7] or '0'),"[%.%-?%d]+") itemlist[8]=string.match((itemlist[8] or '0'),"[%.%-?%d]+") end if args['arc-coord'..nval] then local itemTab=splitItem(convertCoordsTrad (args['arc-coord'..nval]),2) maplist.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) maplist.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) else maplist.lat=tonumber(string.match(args['arc-lat'..nval] or itemlist[2] or '0',"[%.%-?%d]+")) maplist.lon=tonumber(string.match(args['arc-lon'..nval] or itemlist[3] or '0',"[%.%-?%d]+")) end local arcX,arcY=maptogrid(maplist,6) arcText = args['arc-text'..nval] or arcText local fontSize =tonumber(args['arc-text-size'..nval] or itemlist[4] or '12') local textColor=getColor(string.gsub(args['arc-text-color'..nval] or itemlist[5] or 'grey','[%s]+','') ) local arcAngle= tonumber((args['arc-angle'..nval]) or (itemlist[6]) or '45')-90 local arcRadius =tonumber(args['arc-radius'..nval] or itemlist[8] or '0.05') local arcGap = tonumber(args['arc-gap'..nval] or itemlist[7] or '1')* ( ( math.sin(8-math.rad(arcRadius))^8 )+0.4 )*( ( fontSize+6 )/15 ) arcRadius=450*arcRadius*0.75 local ellipseFactor=tonumber(args['ellipse-factor'..nval] or itemlist[9] or '1') local arcRotate =arcAngle+90 if arcGap<0 then arcRotate=arcAngle-90 end local latF=arcY - fontSize + (0-(math.sin(math.rad(arcAngle))) * arcRadius) local lonF=arcX - fontSize +(0-(math.cos(math.rad(arcAngle))) * arcRadius) local step=1 for codepoint in mw.ustring.gcodepoint( arcText ) do -- block step=1,#arcText do table.insert(result,'<div style="position: absolute;') local posY=tostring(round( (latF + (math.sin(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius)) *ellipseFactor,2))..'px;' local posX=tostring(round( (lonF + (math.cos(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius))/ellipseFactor,2))..'px;' table.insert(result,' top: '..posY..' left: '..posX..' transform: rotate( '..tostring(round(arcRotate +((step-1)*arcGap)),2)..'deg);') table.insert(result,'width:'..tostring(fontSize*2)..'px; text-align: center; background-color:transparent; color: '..textColor..';') table.insert(result,'vertical-align: baseline; font-size: '..tostring(fontSize)..'px;">'..mw.ustring.char(codepoint)..'</div>') step=step+1 end end local function makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) local item={} itemdescription=stripdivs(itemdescription or '') local templon=lon if lon > 180 then templon=lon-360 end --for hemisphere+ or -1 dots if lon < -180 then templon=lon+360 end -- use 'real' coordinates for geohack label, while retaining shifted coords for plot if itemcolor=='transparent' then itemcolor='white' end itemcolor=getColor(itemcolor) -- ensure no opacity, which breaks maplink if string.find(itemcolor,'#')==1 and #itemcolor>7 then itemcolor=string.sub(itemcolor,1,7) end table.insert(item, '{ "type": "Feature", "properties": {') table.insert(item, ' "title": "'..itemtitle..'",') table.insert(item, ' "description": "'..itemdescription) table.insert(item, ' ([https://geohack.toolforge.org/geohack.php?params='..tostring(lat)..';'..tostring(templon)) table.insert(item, '_dim:2000 '..tostring(lat)..','..tostring(templon)..'])",') table.insert(item, ' "marker-symbol": "-number-'..string.gsub(group,'%W','')..'", "marker-size": "medium", "marker-color": "'..itemcolor..'" },') table.insert(item, ' "geometry": {"type": "Point", "coordinates": ['..tostring(lon)..','..tostring(lat)..'] } }') return table.concat(item) end local function makeLegendBox(result,args) local legend ={} local line,count, maxWidth='',1,8 local item line,item=extractItem(args.legendBox or '') local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end legend.Text = a -- convert hats to line breaks line=splitItem(line,6) -- (text, size, poition,background color, text/outline color, param options) legend.Size=line[2] or '150px80px1px' legend.Pos=line[3] or '10px10px' legend.Background=line[4] or 'beigeground' legend.Color=line[5] or 'darkbrown' legend.Param= line[6] or '' local argnum,legendCount,titleHeight='1',1,0 if (legend.Text and legend.Text~='') then titleHeight=15+(13.4*(select(2, string.gsub(legend.Text,"<br>", "")))) end local legendLine,legendGroup,legendY={},{},{} while args['legendItem'..argnum] do -- assign legendLine, legendGroup, legendY for each dot line,legendLine[legendCount] = extractItem(args['legendItem'..argnum] or '') line=splitItem(line,3) legendGroup[legendCount]=line[2] or argnum if shapeList[legendGroup[legendCount]] then maxWidth=math.max(tonumber(string.match(shapeList[legendGroup[legendCount]].shapeSize or '10','[%d]+')),maxWidth) else maxWidth=math.max(tonumber(string.match(shapeList['1'].shapeSize or '10','[%d]+')),maxWidth) end maxWidth=maxWidth+1 if line[3] then legendY[legendCount]=tonumber(string.match(line[3],'[%d]+')) else legendY[legendCount] = 3+maxWidth*(legendCount-1)+titleHeight -- if (legend.Text and legend.Text~='') then legendY[legendCount]=legendY[legendCount]+15 end end legendCount=legendCount+1 argnum=tostring(legendCount) end local w,h,r=getsize(legend.Size or '') local x,y=getsize(legend.Pos or '') local div=mw.html.create ('div') div:css('position', 'absolute') div:css('outline', '1px solid'..getColor(legend.Color)) if r~=0 then div:css('border-radius',tostring(r).."px") end div :css('top', y.."px") :css('left', x.."px") :css('width', w.."px") :css('height', h.."px") :css('line-height','105%') :css('background-color', getColor(legend.Background) ) :css('color','inherit') if not string.find(legend.Param,'noshadow') then div:css('box-shadow', '2px 2px 4px #33203335') end div:tag( 'div' ) :css('position', 'absolute') :css('top','1px') :css('left', (w/2).."px") :css('width',(w-8)..'px') :css('text-align', 'center') :css('color', getColor(legend.Color)) :css('transform', 'translateX(-50%)') :css('font-size','11px') :wikitext(legend.Text) :done() for lct=1,legendCount-1 do --local t=legendGroup[lct] local shape=shapeList[legendGroup[lct]] or shapeList['1'] local row={} row.gridx=3+maxWidth/2 row.gridy=(legendY[lct] or 0) + 5 if shape.shape=='image:' then row.gridy=row.gridy+6 end row.dx=0 row.dy=0 local legendShape= getshapetable(row,shape) div:wikitext(legendShape) :tag( 'div' ) :css('position', 'absolute') :css('top',(legendY[lct] or 0)..'px') :css('left', (maxWidth+6)..'px') :css('width', (w-maxWidth-6)..'px') :css('text-align', 'left') :css('line-height','103%') :css('color', getColor(legend.Color)) :css('font-size','10px') :wikitext(legendLine[lct]) :done() end div:allDone() table.insert(result,tostring(div)) end local function getQset (qValue,pValue,argNumber,args) local result={} local sortType=string.lower(args['geo-list-sort'] or 'a') local testValue='0' if sortType == 'alpha' or sortType == 'a' or sortType == 'ah' or sortType == 'al' then testValue = mw.wikibase.getLabel(qValue) elseif sortType == 'value' or sortType == 'v' or sortType == 'vh' or sortType == 'vl'then if qValue==nil or pValue == nil then debugmsg(mw.addWarning((qValue or 'nil')..' Qnumber for '..(pValue or 'nil'))) else if mw.wikibase.getBestStatements(qValue,pValue)[1] then testValue = mw.wikibase.getBestStatements(qValue,pValue)[1].mainsnak.datavalue.value.amount or '0' if pValue == 'P2046' then testValue = convertToSqKm(testValue, mw.wikibase.getBestStatements(qValue,pValue)[1].mainsnak.datavalue.value.unit) end end end elseif sortType == 'north' or sortType == 'n' or sortType == 'nh' or sortType == 'nl' then if mw.wikibase.getBestStatements(qValue,'P625')[1] then testValue = mw.wikibase.getBestStatements(qValue,'P625')[1].mainsnak.datavalue.value.latitude or 0 end elseif sortType == 'west' or sortType == 'w' or sortType == 'wh' or sortType == 'wl' then if mw.wikibase.getBestStatements(qValue,'P625')[1] then testValue = mw.wikibase.getBestStatements(qValue,'P625')[1].mainsnak.datavalue.value.longitude or 0 end end result[1]=testValue result[2]=argNumber return result end function p._main ( args ) local result={} local frame=mw.getCurrentFrame() local dotTable={} local magVal,scaleVal = args.magnify,'' local origH,origW=maplist.height,maplist.width if magVal then --set up the values needed to magnify the top-right portion of the map magVal=tonumber(string.match(magVal or '0',"[%.%-?%d]+")) or 1 if magVal>1 and magVal <=2 then maplist.height= round(maplist.height/magVal,0) maplist.width=round(maplist.width/magVal,0) scaleVal='transform: scale('..magVal..') translateY('..tostring((origH-maplist.height)/2)..'px);' else magVal=1 end end -- set up the three nested div boxes (plus an extra if centered) to put the map plus title/caption area, in an appropriate frame on the page if args.float=='center' or args.float=='centre' then table.insert(result,'<div class="center"><div class="thumb tnone">') elseif args.float=='left' then table.insert(result,'<div class="thumb floatleft">') else table.insert(result,'<div class="thumb floatright">') end table.insert(result,'<div class="thumbinner" style="position: relative; top: 0px; right: 0px; width: '..(args.width or "400")..'px;">') if args.title then table.insert(result,'<div class="center" style="font-weight:bold">'..args.title..'</div>') end if magVal and magVal >1 and magVal<=2 then table.insert(result,'<div class="thumbinner noresize" style="display:block; position: relative; outline:0px; border:0px; padding:0px; background-color:transparent; color:inherit;') table.insert(result,'top: 50%; right: '..tostring(0-((origW-maplist.width))/2)..'px; ') table.insert(result,'height: '..origH..'px; width: '..maplist.width..'px; '..scaleVal..'">') else magVal=1 table.insert(result,'<div class="thumbinner noresize" style="position: relative; outline:0px; border:0px; padding:0px;') table.insert(result,'top: 0px; right: 0px; ') table.insert(result,'height: '..maplist.height..'px; width: '..maplist.width..'px ">') end --add any boundary Qvalues to map-data-light if requested if args['geo-region'] and (args['geo-boundaries'] == '1') then local qmaps=args['map-data-light'] or '' if qmaps ~='' and string.sub(qmaps,#qmaps) ~= ',' then qmaps=qmaps..',' end local geoRegion = args['geo-region'] -- or add base-page here local geoSubRegion = args['geo-sub-region'] or 'P150' if not mw.wikibase.isValidEntityId(geoSubRegion) then geoSubRegion = 'P150' end if mw.wikibase.isValidEntityId(geoRegion) then local ct=1 local qtemp while (mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct]) do -- debugmsg('table '..ct..' - '..mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id) qtemp = mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id or nil if qtemp then if mw.wikibase.entityExists(qtemp) then qmaps=qmaps..qtemp..',' -- see if there is a geocode boundary Qvalue to add else debugmsg(mw.addWarning('unable to add geocode value for '..mw.wikibase.getLabel..','..qtemp)) end end ct=ct+1 end end if qmaps and qmaps ~= '' then -- add back the created/expanded list of boundary Qvalues, and remove trailing comma args['map-data-light'] = string.sub(qmaps,1,#qmaps-1) end end -- Create the basemap using mapframe local mapframecontent=getmapframecontent(args,'basemap') table.insert(result, frame:extensionTag{ name ='mapframe', content=mapframecontent, args={width=tostring(maplist.width), height=tostring(maplist.height), zoom=tostring(maplist.zoom), longitude=tostring(maplist.lonbase), latitude=tostring(maplist.latbase), mapstyle=maplist.mapstyle, frameless=true } } ) --Add coverall box to block the unhelpful links from mapframe - which wouldn't include all the dots. Reinstate some links to osm and wikimedia table.insert(result,'<div style="position: absolute;width:'..tostring(maplist.width)..'px; height:'..tostring(maplist.height)..'px;') table.insert(result,'top:0px;left:0px;background-color:#FFFFFF00; color:inherit;"></div>') --Add replacent hover-links for OpenStreetMap and maps terms and conditions table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-13)..'px; width: 12px; height: 12px">') table.insert(result,'[[file:Transparent.svg|12px|link=https://www.openstreetmap.org/copyright|'..aboutOSM..']]</div>') table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-110)..'px;') table.insert(result,'width: 12px; height: 12px background-color: transparent; color:inherit;">') table.insert(result,'[[file:Transparent.svg|12px| link=https://foundation.wikimedia.org/wiki/Policy:Maps_Terms_of_Use|'..termsOfUse..']]</div>') -- Add scale-line if not args.scalemark or args.scalemark~='0' then local top=maplist.height-42 local left=maplist.width-61-(tonumber(args.scalemark or '1')) local minipos=string.lower(args.minimap or '') -- scalemark gets pushed left if it would be behind the minimap if minipos~='' and not(string.find(minipos,'left') or string.find(minipos,'top') ) then local offset=tonumber(args.scalemark or '1')-tonumber(args['mini-width'] or '60') if offset<1 then left=maplist.width-61-tonumber(args['mini-width'] or '60') end end if maplist.width-left >216 then top=top+14 end -- shunt scaleline down if it is beyond the copyright stuff local scalek,scalem=getScale(maplist.zoom, maplist.latbase, magVal) local magReduce='' table.insert(result,"<div style=\"display:inline-block; position: absolute; background-color: #111111") table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;M0,8 l0,4 l20,0 l0,-4 l-0.3,0 l0,3.7 l-19.4,0 l0,-3.7 z&#39;) ") table.insert(result,"; width:20px") -- path is a 20px20px box, and then rescales if magVal==1 then table.insert(result,"; top:"..tostring(top-1).."px") table.insert(result,"; left:"..tostring(left+16).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5)..", "..tostring(1.5)..")") else -- shrink the scalemark to compensate for magnification table.insert(result,"; top:"..tostring(top-(magVal*0.28)).."px") table.insert(result,"; left:"..tostring(left+(16*(magVal*1.15))).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5*(1/magVal))..", "..tostring(1.5*(1/magVal))..")") magReduce= 'scale('..tostring(1/magVal)..')' end table.insert(result,"\"></div>") table.insert(result,'<div style="position: absolute; top: '..tostring(top)..'px; left: '..tostring(left+47)..'px; font-size: 9.5px; line-height: 126%; width: fit-content;') table.insert(result,'color: #444433; background-color: transparent; text-align: right; transform: '..magReduce..' translateX(-100%);">'..scalek..'<br>'..scalem..'</div>') end --Set up the shapeList and dotList tables, to provide data to go on the map local sgNumbers,sgSortable={},{} --s1,s2 sgNumbers["1"]="1" if args.useFormatStyle and args.useFormatStyle=='shortstyle' then shapeList=ParseShapeTypes (shapeList,args,"1") for argindex,argv in pairs(args) do -- build a list of all the numbered sg's that have been used if string.find(argindex,"sg[a-f,n%d]+") == 1 then -- only look through the sga,sgb, sgc,sgd,sge,sgf and sgn args local x=string.match(argindex,"[%d]+") -- find its number if x and not sgNumbers[x] then sgNumbers[x]=x end -- only add if not already found end if string.find(argindex,"sg[a-f]H") == 1 and highlightNum then highlightOption=true end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form for k,v in pairs(sgSortable) do -- work through the sorted list, parsing each set of sg's in turn, from 1 upwards shapeList=ParseShapeTypes (shapeList,args,v) end if highlightOption==true then shapeList=ParseShapeTypes (shapeList,args,'H') end end local dotList,dotresult,dotItemTable,dotGroupList={},{},{},{} if (not args.useFormatStyle) or args.useFormatStyle=='standardstyle' then local dotmax=0 local qset={} for indx,val in pairs(args) do if string.match(indx,'mark%-coord[%d]+') or string.match(indx,'lat[%d]+') or string.match(indx,'geo%-data[%d]+') then dotmax=math.max(dotmax, tonumber(string.match(indx,"[%d]+"))) if string.match(indx,'geo%-data[%d]+') then local argnum=string.sub(indx,9,#indx) or '' if args['mark-title'..argnum] == 'none' or (string.find(args['mark-title'..argnum] or 'x','nolist')== 1) then -- debugmsg('sort values ignored '..argnum..', '..args['mark-title'..argnum]) else qset[val]=getQset (val, (args['geo-data-type'] or 'P1082'), argnum, args) end end end if (indx=='shapeH') or (indx=='shape-colorH') or (indx=='shape-outlineH') or (indx=='label-colorH') then highlightOption=true end end local qlist=args['geo-data-list'] if qlist then if string.sub(qlist,#qlist) ~= ',' then qlist=qlist..',' end else qlist='' end local geoRegion = args['geo-region'] -- or add base-page here local geoSubRegion = args['geo-sub-region'] or 'P150' if not mw.wikibase.isValidEntityId(geoSubRegion) then geoSubRegion = 'P150' end if (args['find-geo-dots'] or '1') ~= '0' and geoRegion and mw.wikibase.isValidEntityId(geoRegion) then local ct=1 local qtemp while (mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct]) do -- debugmsg('table '..ct..' - '..mw.wikibase.getBestStatements(adminRegion,adminSubRegion)[ct].mainsnak.datavalue.value.id) qtemp = mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id or nil if qtemp then qlist=qlist..qtemp..',' -- add subregion Qvalue to the list end ct=ct+1 end end if qlist and #qlist>1 then -- create a table from the csv list local _, c = qlist:gsub(",","") local qtab=splitItem(qlist,c) for xd,yd in pairs(qtab) do yd=string.upper(yd) if qset[yd] == nil and mw.wikibase.isValidEntityId(yd) then dotmax=dotmax+1 qset[yd]=getQset (yd, (args['geo-data-type'] or 'P1082'), tostring(dotmax), args) args['geo-data'..tostring(dotmax)] = yd -- debugmsg(dotmax..' = '..(string.upper(yd) or 'Q Not found')..': '..(mw.wikibase.getLabel(yd) or 'label Not found')..': '..(mw.wikibase.getSitelink(yd) or 'link Not found')) elseif not mw.wikibase.isValidEntityId(yd) then debugmsg(mw.addWarning(yd..' is not a [[wikidata]] page. geo-data-list needs comma-separated Qvalues' )) else debugmsg(mw.addWarning(yd..' is duplicate')) end end end -- qset contains set of Qvalues and can be from wikidata geo-region, list of geo-data-list Q values or individual geo-data1= etc local ts = {} -- table to hold the keys local useQset=false for ky, val in pairs(qset) do table.insert(ts, {key = ky, value = val[1], num=val[2]}) useQset=true end if useQset then local order = string.lower(args['geo-list-sort'] or 'a') -- sort the list from previously stored sortkey, either Lowest or Highest first if string.find(',al,vl,nl,wl,west,w,alpha,a,' , ','..order..',') then table.sort(ts, function (a,b) return lessthan(a.value, b.value) end) else table.sort(ts, function (a,b) return morethan(a.value, b.value) end) end for ky,val in ipairs(ts) do -- assign values from sorted list to numbered parameter args['numbered'..val.num] = ky -- The numbered parameter becomes the value for each dot that has a geo Qvalue, from manual, list or wiki sources. end end dotItemTable=tradstyleParseShapes(args,dotItemTable,dotmax) for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) or args['geo-data'..x] then sgNumbers[x]=x -- add it to the list end end else for indx in pairs(args) do if string.match(indx,'dot[%d]+') then table.insert(dotList,indx) end --add the index name for dot1, dot2 etc to dotList end table.sort(dotList,morethan) for indx,dotName in pairs(dotList) do dotresult=ParseData(args,string.match(dotName,'[%d]+') ) -- using each dot number, assign the settings for each dot to a dotresult item line table.insert(dotItemTable,dotresult) -- and store that item line within the dotItemTable end end for arcVal = 65,91 do -- check through args looking for any arcs local arcLetter=string.char(arcVal) if args['arc-text'..arcLetter] or args['arc'..arcLetter] then makeArcText(args,result,arcLetter) end end local dotdivs='' local ddots=0 if dotItemTable[1] then local ddots=(dotItemTable[1].lat or 0)+(dotItemTable[1].lon or 0) end local fgroup='F'..tostring(maplist.latbase+maplist.lonbase+ddots ) local FullscreenList={} local addcomma='' for i,dotitem in pairs(dotItemTable) do -- working throug each dot item, merge the dot and shape values into a full set of css text local dotgroup= dotitem.group or "0" if dotitem.posType=='mark-line' and dotitem.markDest then --find destination xy values for any mark-lines for n,v in pairs(dotItemTable) do if v.code == dotitem.markDest then dotitem.gridx2=v.gridx dotitem.gridy2=v.gridy break end end end local qtype=dotitem.group -- find which shape group each dot has been assigned --debugmsg('dotgroup='..qtype..', sg='..(sgNumbers[qtype] or 'nil')..' , shapeList='..shapeList[qtype].shape) if not sgNumbers[qtype] then qtype="0" end --shapeList[dotitem.group] will give access to the shape values for that dot if highlightNum==dotitem.code and highlightOption==true and shapeList['H'] then table.insert(result, getshapetable(dotitem,shapeList['H'])) else table.insert(result, getshapetable(dotitem,shapeList[qtype])) -- Add the actual css instructions for each dot end if shapeList[dotgroup] and not string.find((dotitem.param1 or ''),'nomap') then -- only add if not excluded with 'nomap' labelText local ftext='' if dotitem.dotTag~='' and not string.match(dotitem.labelText or '','[%d]') then ftext=stripdivs(dotitem.dotTag or '')..' <br>' end if (dotitem.labelText ~= ftext) and dotitem.dotLink =='' then ftext=ftext..' '..stripdivs(dotitem.labelText)..'<br>' end if (dotitem.dotLink) and (dotitem.dotLink ~='') then ftext=ftext..dotitem.dotLink..'<br>' end if dotitem.imageName then ftext=ftext..'[[File:'..dotitem.imageName..'|250px]]' end table.insert(FullscreenList,1, makeFullscreenItem (string.gsub(ftext,"[\n]+"," "), dotitem.info,round(dotitem.lat,5),round(dotitem.lon,5),fgroup,shapeList[dotgroup].shapeColor)..addcomma ) addcomma=', ' end -- makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) -- Always add to start of list, to reverse the sequence, and separate with commas except for first item, which is now at the end end if args.legendBox then makeLegendBox(result,args) end if args.minimap or args['mini-locator'] then makeLocatorMap(args,result) end -- add tag link and details for fullscreen version addcomma='' if (mapframecontent or '[]') ~= '[]' then addcomma=',' end mapframecontent=getmapframecontent(args,'fullscreen') local contentstart='[ '..mapframecontent..addcomma..'{ "type": "FeatureCollection", "features": [ ' --extra features after first square bracket local contentend=' ] } ]' table.insert(result, '<div style="position: absolute;top: 9px;left: '..tostring(maplist.width-34)..'px">') table.insert(result, '<div style="color: white; opacity:100; font-size: 19px; font-weight:normal; text-align: left;">') table.insert(result, frame:extensionTag{ name ='maplink', content=contentstart..table.concat(FullscreenList)..contentend, args={zoom=tostring(maplist.zoom+1), class='no-icon', frameless='1', latitude=tostring(maplist.latbase), longitude=tostring(maplist.lonbase), --add invisble 'en-spaces' for tooltip text='<div title="'..fullscreenlinktext..'">&nbsp;&nbsp;&nbsp;</div>'} } ) table.insert(result,'</div></div>') -- end of maplink ----- -- add closing div for main map table.insert(result,'</div>') -- collate caption material to go in the outer div class if captionTable ~= '' and (not args['auto-caption']) then args['auto-caption']='on' end --default to include list if using geo values local autocaption=string.lower(args['auto-caption'] or 'no') local autoOff=autocaption:match("(%w+)(.*)") -- select the first word in autocaption and see if it is a negativeAnswer) if negativeAnswer[autoOff] then captionTable='' end if args.caption or (not negativeAnswer[autoOff]) then table.insert(result,'<div class="thumbcaption" style="text-align:left">') if args.caption then table.insert(result,args.caption) if captionTable ~= '' then table.insert(result,'<hr>') end end end local columns=tonumber(autoOff:match("[%d]+") or '1') if columns>1 then columns=round(maplist.width/(columns*17), 0) end -- convert from em to px for historical reasons --for k in pairs(dotList) do capchk=capchk..(args["dotlink"..k] or '') end local capchk=nil local captionList = {} for key, value in pairs(dotItemTable) do -- only add an autocaption line if there is both a dotTag and a dotLink line available and it is not marked as nolist if value.dotTag and value.dotTag~='' and (not string.find(value.param1 or '','nolist')) and string.gsub(value.dotLink or '',"%s+","")~='' then table.insert(captionList, {key = key, value = value}) if captionTable == '' then capchk=true else -- add the next line of content to the html table capchk=false end end end if captionTable ~= '' then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) for k,v in pairs(captionList) do local nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag local ngrp=v.value.group if captionTableText[ngrp] then -- debugmsg('table text for'..v.value.dotTag..', '..captionTableText[v.value.group][1]..', '..captionTableText[v.value.group][2]..', '..captionTableText[v.value.group][3]..' ('..captionTableText[v.value.group][4]..')') local c1,c2 c1,c2=checkColors(shapeList[ngrp].shapeColor) local cDot='<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;' cDot=cDot..'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div>' captionTable = htmlTableLine(captionTable,cDot,captionTableText[ngrp][1], captionTableText[ngrp][2], captionTableText[ngrp][3], captionTableText[ngrp][4]) end end end if capchk and (not negativeAnswer[autoOff]) then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) -- local myDivision = string.gsub((args.toggletext or toggletext), "%s+", "") if string.find(autocaption,'collaps') then table.insert(result,'<div class = "wikitable mw-collapsible ') if string.find(autocaption,'collapsed') then table.insert(result,'mw-collapsed') end table.insert(result,'" style="width:100%; line-height: 17px; border-style:none; text-align: center"><b>'..(args.toggletext or toggletext)..'</b>') end if string.find(autocaption, 'columns=') then columns=string.match(autocaption,'[%d]+',string.find(autocaption, 'columns=') ) end table.insert(result,'<div class="mw-collapsible-content" style="column-count:'..columns..'; column-rule:solid 1px;text-align:left;padding-top:5px">') table.sort(dotList,lessthan) local nval,ngrp='','0' for k,v in pairs(captionList) do nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag ngrp=v.value.group or '0' if v.value.dotLink and v.value.dotLink~='' and nval and nval~='' then local c1,c2 if nval==args.highlight then c1,c2=checkColors(shapeList['H'].shapeColor) else c1,c2=checkColors(shapeList[ngrp].shapeColor) end table.insert(result,'<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;') table.insert(result,'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div> '..v.value.dotLink..'<br>') end end table.insert(result,'</div>') -- end for caption-content div if string.find(autocaption,'collaps') then table.insert(result,'</div>') end -- end for toggle frame end if captionTable ~= '' then table.insert(result,captionTable..'</table>') end if args.caption or (not negativeAnswer[autoOff]) then table.insert(result,'</div>') end -- end for whole caption frame table.insert(result,'</div></div>') -- outer two frames if args.float == 'center' or args.float=='centre' then table.insert(result,'</div>') end if args['show-new-format'] == 'hints' then -- provide a 'format hint panel' in the 'Preview Box' local w="<small>Below are some template hints for the 'sga' compressed version, " w=w..'{{tl|OSM Location dots}}. It can use these, instead of the more verbose {{tl|OSM Location map}} parameter format. ' w=w..'Data is divided between a "ShapeGroup" and the "Dots", so that a single shapeGroup can be used for multiple dots on the map. ' previewMsg(w..'(nb. the "group" value can be the number or an assigned name of a shapeGroup)</small>') w='<small>{{tl|OSM Location dots}}: | dot(n)=group,lat,lon,dotTag | ' w=w..'dotlink=link/tooltip | dotlabel=label,position,dx,dy,param1,info | dotpic=filename <br>' w=w.."(nb. param1 options include 'nolink' 'nolist' 'nomap' 'hemisphere-1' 'hemisphere+1', 'noline' - quotes not required, separate with spaces).<br>" w=w..'| sga = Shape,Sizepx,Color,Angledeg | sgb= OutlineWidth,Color,Style | sgc=TextSize,Color,Angle,bold italic <br>' w=w..'| sgd=TagSizepx,Color,Spacer,Angledeg | sge=LineWidth,Color,Style | sgf=TextSpacingpx,LineHeight%,Outlinepx,backgroundColor<br>' w=w..'| sgn=Name (optional, to assign a meaningful name) | sgp=Parent (can be the name or number of the parent shapeGroup. ' previewMsg(w..'Each shapegroup will inherit values from a parent, stretching back to "sga1" and its default values.)</small>') if #pmsg then local dbg={} for i,x in pairs(pmsg) do table.insert(dbg,x..'<br>') end dbg=mw.addWarning(table.concat(dbg)) table.insert(result, dbg) end end if args.coordtest then debugmsg(mw.text.nowiki(args.coordtest)) end for i,x in pairs(msg) do table.insert(result, x..'<br>') end return table.concat(result) end function p.main(frame) local args = getArgs(frame) local itemTab={} maplist.width=tonumber(args.width) or 400 maplist.height=tonumber(args.height) or 300 if args.coord then itemTab=splitItem(convertCoordsTrad (args.coord),2) maplist.latbase=itemTab[1] maplist.lonbase=itemTab[2] else maplist.lonbase=tonumber(args.lon) or 5 maplist.latbase=tonumber(args.lat) or 0 end maplist.zoom=tonumber(args.zoom) or 1 visibleLinks=args.showlinks highlightNum=args.highlight if args.nolabels=='1' then maplist.mapstyle='osm' else maplist.mapstyle='osm-intl' end if maplist.width >= 400 and not(args['show-q-values']) then args['show-q-values'] = '1' -- default to on if wider than 400px elseif maplist.width < 320 then args['show-q-values'] = '0' end -- turn off Qvalus if frame is too narrow, even if requested return p._main(args) end return p n1ouydkukif136v1lyxxtbx7a56z1oc 204459 204458 2026-07-05T14:58:52Z HaxelKaszëba 17492 204459 Scribunto text/plain require('strict') local delink=require('Module:Delink').delink local getArgs = require('Module:Arguments').getArgs local p = {} local maplist={} local sgNames={} local highlightOption=false local highlightNum local visibleLinks local geoTotalPop = 0 local captionTable = '' local captionTableText = {} -- This module creates framed maps of anywhere in the world, at the required scale, and enables annotations, -- dots, shapes, lines and other ways to customise the area of the map being shown. It also provides a link -- to an interactive fullscreen version, which has locator dots instead of annotations and shapes. -- This is the 2025 successor module to a wiki-markup template version of 2024, which itself was a successor -- to the 'Graph'/VEGA driven template that was begun in 2016, until the Vega version was switched off in 2023. -- This module is called from template {{OSM Location map}}, which uses the same parameter formats as before. -- In addition it may at some point be possible to use a more concise parameter format using the template {{OSM Location dots}} -- In general the css output from the two formats will be identical, but the the concise version will allow bits of -- greater control over some of the settings. -- see the documentation on the two template pages for details of how to use the mapping features. -- If language customisation is needed, there are text items below that can be translated. Also see the color table -- below with details of how to add additional color names to allow localised alternatives. -- (Translating other language shape-types could be possible, but has not currently been contemplated. -- Parameter name translation would be harder but likely to be possible, ideally still retaining compatibility -- with template calls already written using English). -- -------------- the following text variables are used within the template, and can be translated as required --------------- -- -------------- you may wish to retain the English version as a comment, to keep track of the original phrase -------------- local negativeAnswer={no=1,'0'-1,off=1} local fullscreenlinktext = 'Click for interactive fullscreen map with links to nearby articles' local toggletext = 'List of numbered map items:' local termsOfUse = 'Maps: terms of use' local aboutOSM = 'About OpenStreetMaps' local yards = 'yds' local miles = 'miles' local metres = 'm' local km = 'km' local sqkm = 'sq.km' local year = 'year' local notFound = 'not found' local totalItems = 'Total' local sumOfDatapoints = 'sum of datapoints' local numberCol = 'No.' -- abbreviated for top of column of code numbers local wikidataLink = 'Wikidata page' local shapeList={} --This sets up the 'factoryDefault' shape group 0 shapeList["0"]={shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border of the shape outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items text settings for labels textSZ="11px", textCL="darkgrey", textNG="0deg", --sgf further text settings textSP="0px", textLH="120%", textOL="0px", textBG="transparent", --sgd items for dotTag text settings tagSize="10px", tagColor="white", tagSpacer="0px", tagAngle="0deg", --sge items for extension line to connect label to dot textEW="0px", textEC="darkgrey", textES="solid" } local colorList={} -- used by colorLookup to catch unsupported colors (eg 'LimeGreen'), to convert to generic version colorList['green']='hardgreen' -- it could also be added to to include alternative language equivelants, for a quick solution. colorList['red']='hardred' -- colorList ['source'] = target colorList['white']='white' -- converts any color that includes 'source' into its equivelent target colorList['blue']='hardblue' -- note, for translation you can add to this list, rather than replace it, colorList['brown']='brown' -- which would mean existing map definitions in english would also still work, alongside translated ones colorList['grey']='hardgrey' colorList['gray']='hardgrey' colorList['purple']='hardpurple' colorList['orange']='hardorange' colorList['leaf']='hardleaf' --for a more thorough translation, you can add all the variants of the colors as further CTB elements and hex values or redirects local CTB={} -- set up a table of color names (the CTB Color table index) and html hash colorhex values. CTB["paleblue"],CTB["softblue"],CTB["hardblue"],CTB["darkblue"]="#D6E1EC","#77A1CB","#4B77D6","#1c559e" CTB["palered"],CTB["softred"],CTB["hardred"],CTB["darkred"] = "#FCC6C0","#EC644B","#DB3123","#AA1205" CTB["palegreen"],CTB["softgreen"],CTB["hardgreen"],CTB["darkgreen"]= "#D2F0E5","#81AF81","#269F46","#0b7527" CTB["paleleaf"],CTB["softleaf"],CTB["hardleaf"],CTB["darkleaf"]= "#dff5c1","#b5e376","#8cc244","#679c21" CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"]= "#E8E8D6","#AAAA88","#777755","#444433" CTB["palegray"],CTB["softgray"],CTB["hardgray"],CTB["darkgray"]=CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"] CTB["palebrown"],CTB["softbrown"],CTB["hardbrown"],CTB["darkbrown"]="#FAF6ED","#CCB56C","#AD7F14","#754910" CTB["palepurple"],CTB["softpurple"],CTB["hardpurple"],CTB["darkpurple"]="#e0d1e6","#c784e0","#a029cf","#7a05a8" CTB["paleorange"],CTB["softorange"],CTB["hardorange"],CTB["darkorange"]="#ffedc2","#ffcf61","#EEB533","#e39f05" CTB["black"],CTB["white"],CTB["yellow"]="#000000","#FFFFFF","#FAF039" CTB["background"],CTB["paleground"],CTB["beigeground"]="#f9f6f2","#FEFEFA","#F5F5DC" CTB["beige"]=CTB["beigeground"] CTB["aqua"],CTB["teal"],CTB["fuchsia"] = "#00FFFF","#008080","#FF00FF" CTB["maroon"],CTB["olive"],CTB["navy"] = "#800000","#808000","#000080" CTB["lime"],CTB["limegreen"],CTB["aquamarine"] = "#00FF00","#32CD32","#7FFFD4" CTB["silver"],CTB["yellow"],CTB["orchid"] = "#800000","#FFFF00","#DA70D6" -- set up a table of predefined clip-paths local pathshape={} pathshape.squaredd = "M 19,1.25 l 0,18 -18,0 0,-18 18,0m-1,1 -16,0 0,16 16,0 0,-16m-1,1 0,14 -14,0 0,-14 14,0zm-1,1 -12,0 0,12 12,0 0,-12zm-1,1 0,10 -10,0 0,-10 10,0z" pathshape.squared = "M 18,2.5 l 0,15 -15,0 0,-15 15,0m-1,1 -13,0 0,13 13,0 0,-13zm-1,1 0,11 -11,0 0,-11 11,0z" pathshape.triangledd="M 0 20,20 20,10 0,0 20ZM1.5 19,10 1.7,18.5 19,1.5 19ZM3 18,17 18,10 3.8,3 18ZM4.5 17,10 5.4,15.4 17, 4.5,17ZM6 16,13.8 16,10 7.4z" pathshape.triangled ="M1,18 l 18,0 l -9,-18 l -9,18zm1.7,-1.1 l 7.3,-14.6 l 7.3,14.6 l -14.6, 0zm1.7,-1 l 11.0,0 l -5.5,-11 l -5.5,11z" pathshape.circledd = "M0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm0.8,0a9.2,9.2 0 1,1 18.4,0a9.2,9.2 0 1,1 -18.4,0m1,0a8.2,8.2 0 1,0 16.4,0a8.2,8.2 0 1,0 -16.4,0zm0.8,0a7.2,7.2 0 1,1 14.8,0a7.2,7.2 0 1,1 -14.8,0m1,0 a6.4,6.4 0 1,1 12.8,0a6.4,6.4 0 1,1 -12.8,0z" pathshape.circled = "M2.5,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0m0.8,0 a5,5 0 1,1 11.4,0a5,5 0 1,1 -11.4,0" pathshape.diamond = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10z" pathshape.diamondd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm1,0 l 6,8.5 l 6,-8.5 -6,-8.5 -6,8.5zm1,0 l 5,-7 5,7 -5,7 -5,-7z" pathshape.diamonddd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm0.75,0 l 6.25,9 l 6.25,-9 -6.25,-9 -6.25,9zm0.75,0 l 5.5,-8 5.5,8 -5.5,8 -5.5,-8zm0.75,0 l 4.75,7 l 4.75,-7 -4.75,-7 -4.75,7zm0.75,0 l 4,-6 4,6 -4,6 -4,-6z" pathshape.crossd = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0zM2.3,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.cross = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0z" pathshape.thincross = "M2,12 l6,0 l0,6 l4,0 l0,-6 l6,0 l0,-4 l-6,0 l0,-6 l-4,0 l0,6 l-6,0z" pathshape.fivepointstar = "M10 0 L12.245 6.91 19.511 6.91 13.633 11.18 15.878 18.09 10 13.82 4.122 18.09 6.367 11.18 0.489 6.91 7.755 6.91Z" pathshape.fivepointstard = "M10 1.5 L 11.90825 7.3735 18.08435 7.3735 13.08805 11.003 14.9963 16.8765 10 13.247 5.0037 16.8765 6.91195 11.003 1.91565 7.3735 8.09175 7.3735 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sixpointstar = "M10 0 L12.323 5.977 18.66 5 14.645 10 18.66 15 12.323 14.023 10 20 7.677 14.023 1.34 15 5.355 10 1.34 5 7.677 5.977Z" pathshape.sixpointstard = "M10 1.5 L 11.97455 6.58045 17.361 5.75 13.94825 10 17.361 14.25 11.97455 13.41955 10 18.5 8.02545 13.41955 2.639 14.25 6.05175 10 2.639 5.75 8.02545 6.58045 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sevenpointstar = "M10 0 L12.048 5.747 17.818 3.765 14.602 8.95 19.749 12.225 13.69 12.943 14.339 19.01 10 14.72 5.661 19.01 6.31 12.943 0.251 12.225 5.398 8.95 2.182 3.765 7.952 5.747Z" pathshape.sevenpointstard = "M10 1.5 L11.7408 6.38495 16.6453 4.70025 13.9117 9.1075 18.28665 11.89125 13.1365 12.50155 13.68815 17.6585 10 14.012 6.31185 17.6585 6.8635 12.50155 1.71335 11.89125 6.0883 9.1075 3.3547 4.70025 8.2592 6.38495 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.eightpointstar = "M10 0 L11.88 5.46 17.071 2.929 14.54 8.12 20 10 14.54 11.88 17.071 17.071 11.88 14.54 10 20 8.12 14.54 2.929 17.071 5.46 11.88 0 10 5.46 8.12 2.929 2.929 8.12 5.46Z" pathshape.eightpointstard = "M10 0 L10 1.5 L11.598 6.141 16.01035 3.98965 13.859 8.402 18.5 10 13.859 11.598 16.01035 16.01035 11.598 13.859 10 18.5 8.402 13.859 3.98965 16.01035 6.141 11.598 1.5 10 6.141 8.402 3.98965 3.98965 8.402 6.141ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.ring="M2.6,9.5a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.semicircle = "M0,5 C0,18.3,20,18.3,20,5 L 0,5z" pathshape.arrowhead = "M 10,10.2 l 10,2.8 -2,-3 2,-3 -10,2.8 -0.2,0.2z" pathshape.boxd=pathshape.squared pathshape.boxdd=pathshape.squaredd pathshape.ellipsed=pathshape.circled pathshape.ellipsedd=pathshape.circledd local msg={} local function debugmsg(txt) table.insert(msg,txt) end local pmsg={} local function previewMsg(txt) table.insert(pmsg,txt) end local function colorLookup(color) for c,l in pairs(colorList) do if string.find(color,c) then return l end end return color end local function getColor (color,chk) local c local opacity="100" if not color or color=='' then color='hardgrey' end if color=="transparent" then return color end if color=="background1" then color='background' end if string.byte(color,1,1)==35 and (#color == 7 or #color == 9) then c=color elseif string.byte(color,1,1)==35 and #color == 4 then c=string.sub(color,1,2)..'f'..string.sub(color,3,3)..'f'..string.sub(color,4,4)..'f' else local s=color..'1' s= s:sub(0,s:find("%d")-1) opacity=string.match(color,"%d+") if not CTB[s] then s= colorList[s] end -- check for synonyms and translations if not CTB[s] then debugmsg(mw.addWarning('color = '..color..'. The color name is not defined. Used default grey instead')) end c=CTB[s] or CTB.hardgrey end if opacity and (tonumber(opacity) < 100) and string.find(c,"#")==1 and string.len(c)==7 and opacity~="" then local hexval=string.format("%x",(math.floor(tonumber(opacity)*2.55))) c=c..hexval end return c end function p.colorvalue(frame) -- enable external access to the CTB colorTable values. usage: {{#invoke:OSM Location map|colorvalue|color=hard blue}} local c if not frame.args.color or frame.args.color=='' then c='grey' else c=string.lower(string.gsub(frame.args.color,'%s+','')) end return string.upper(string.sub(getColor(c),2)) end local function checkColors(color) local c=getColor(color,'check') local opacity =1 -- calculate colour brightness and return black or white for contrast if c=='transparent' then return c,'#000000',0 end if not (string.find(c,'#')==1) then return c,'#FFFFFF',0 end if #c>8 then opacity= tonumber('0x'..(string.sub(c,8,9)))/500 end local r=tonumber('0x'..(string.sub(c,2,3)))/255 local g=tonumber('0x'..(string.sub(c,4,5)))/255 local b=tonumber('0x'..(string.sub(c,6,7)))/255 if 0.2126 * r + 0.7152 * g + 0.0722 * b / opacity < 0.7 then return c,'#FFFFFF',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity else return c,'#000000',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity end end local function morethan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) and tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%-?%f[%d]%d[,.%d]*%f[%D]') ) b = tonumber(string.match(b, '%-?%f[%d]%d[,.%d]*%f[%D]') or '0') end return a>b end local function lessthan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) and tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%-?%f[%d]%d[,.%d]*%f[%D]')) b = tonumber(string.match(b, '%-?%f[%d]%d[,.%d]*%f[%D]') or '0') end return a<b end local function getsize(size) --size1 is between 1 and 3 values, each with px, equating to width,height,corner-rounding --eg '15px 25px 5px' (spaces are optional) or '18px'. returns three numbers local sizeval = {} for v in string.gmatch(size, "[^px]+") do table.insert(sizeval,v) end sizeval[1] = tonumber(sizeval[1]) or 13 sizeval[2] = tonumber(sizeval[2]) or sizeval[1] sizeval[3] = tonumber(sizeval[3]) or 0 return sizeval[1],sizeval[2],sizeval[3] end local function coord2text(coord) -- looks through the output from {{coord}} to find the lat and long decimal values -- and converts compass points to minus or not-minus, return with separating comma. local lat = string.match(coord,'[%.%d]+°[NS]') local lon = string.match(coord,'[%.%d]+°[EW]') local neg={N="",S="-",W="-",E=""} return neg[string.match(lat, '[NS]')]..string.match(lat,'[%.%d]+')..","..neg[string.match(lon, '[EW]')]..string.match(lon,'[%.%d]+') end local function convertCoordsTrad (row) local coords='' if row and string.find (row,'<span class="geo">') then local a,b=string.find(row,'<span class="geo">') local start=b+1 a,b=string.find(row,"</span>",b) local finish=a-1 coords=string.sub(row,start,finish) coords=string.gsub(coords,'; ',',') end return coords end local function convertCoords (row) local start,finish,lat,lon,coords,says if row then local a,b=string.find(row,"<span class=") start=a while a do -- find the final span> finish=b a,b=string.find(row,"span>",b) end if start then coords= string.sub(row,start,finish) says="" if string.find(coords,'<span class="error">') then error("coord error: badly formed coordinates",0) end coords=coord2text(coords) coords = string.sub(row,1,start-1)..coords..string.sub(row,finish+1) else coords=row end return coords else return "Nothing to see here" end end local function fillCommas(val,max) local line='' if not val then line=',' -- ensure there is some content else line = val --string.lower(string.gsub(val,"%s+","")) -- or strip spaces end if string.find(line,',') == 1 then line=' '..line end -- ensure initial comma is not skipped local _, count=string.gsub(line,",","") -- add enough subsequent commas for all entries line=line..string.rep(',',max-count) while(string.find(line,",,") ) do line=string.gsub(line,",,",", ,") --ensure string.gmatch doesn't ignore any empty items by padding with spaces end return line end local function makeLinkBox(left,top,wid,label, link) local linkBoxName='Transparent square.svg' if visibleLinks or '' =='yes' then linkBoxName='Red hollow square.svg' end local builder = mw.html.create('div') --display:inline-block; local t= string.find(label,'wikidata') -- exclude wikidata from tooltip line if t then label=string.sub(label,1,t-2) -- don't show wikidata value in tooltip if string.find(link,'wikidata') then link = '' end -- don't link on click to wikidata (if there was not another link) end builder :cssText('position:absolute;left:'..tostring(left-1-wid/2)..'px;top:'..tostring(top-1 + math.min(wid/2-12,0) - wid/2)..'px') :wikitext(string.format( '[[File:%s|%dpx|link=%s|%s]]', linkBoxName, wid+2, link, label )) return tostring(builder) end local function extractItem(row,searchItem) -- remove text following a searchItem or start of line, which might be in quote-marks to allow commas local xend,xstart=1,0 if not row then return '','' end if searchItem then xend,xstart= string.find(row or '',searchItem or 'image:') end if not xstart then return string.gsub(string.gsub(row or '',"%b\"\"", ''),"%b\'\'", '') or '','' end while row:byte(xstart+1) == 32 and xstart<#row do -- skip over any leading spaces xstart=xstart+1 end local xbyte=row:byte(xstart+1) if xbyte == 34 or xbyte == 39 then -- are they wrapped in single or double quotes xstart=xstart+1 xend=row:find(string.char(xbyte),xstart+1) else xend = row:find(',',xstart+1) -- if no quotes, we assume no commas if not xend then xend=#row+1 end end -- return residual row and extracted text return row:sub(0,xstart)..row:sub(xend), row:sub(xstart+1,xend-1) end local function itemCheck(item,ext) if not item then return nil end if not ext then ext='' end return (string.match(item,"[%.%-?%d]+") or '0')..ext end local function stripdivs(line) return string.gsub(line or '',"%b<>", ' ') end local function splitItem(item,max) -- takes a commas-sep list and returns a table of lowercase items with no spaces, or nil local r={} local x=1 item=string.lower(fillCommas(item,max)) for t in string.gmatch(item,"[^,]+") do r[x]=string.gsub(t,"%s+","") if r[x]=='' then r[x]= nil else -- residual items might have commas if x>max then r[max]=(r[max] or '')..', '..r[x] end end x=x+1 end return r end local function ParseShapeTypes (result,args,sgval) -- for use with compressed, comma-separated 'sg plus dots' parameters --[[ shape table items and default values as set at top of page shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items label text textSZ="11px", textCL="white", textNG="0deg", textAT=attributes ("bold" and/or "italic") --sgd items for dotTag tagSize="11px", tagColor="darkgrey", tagSpacer="0px", tagAngle="0deg", --sge extension line textEW="1px", textEC="darkblue", textES="solid" --sgf fx for text labels textSP="0px" textLH="100%" textOL="1px", textBG="paleground", <!--| sga = shape,Size,Color,Angle|sgb= outlineWidth,color,style eg: sga1=circle,14px,blue,0deg| sgb1=0px,dark grey,solid | sgc=textSize,color,angle,bold/italic | sgd=tagSize,Color,Spacer,Angle eg: sgc1=11px,dark grey,0deg,normal| sgd1=9px,white,0px,0deg | sge=lineWidth,color,style |sgf=textspacing,lineHeight%,outlinepx,backgroundcolor, [bold,italic] eg: sge1=0px,black, solid| sgf1=0px,120%,0px,background | dot=shape-group/lat/lon/title/dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> --]] if args["sgn"..sgval] then local sgname=string.match(args["sgn"..sgval],"(%w+)(.*)") sgNames[sgname]=sgval end local parent= args["sgp"..sgval] if parent then parent=string.match(parent,"(%w+)(.*)") local pos= string.find(parent,"%d+") if pos == 1 then parent=string.match(parent,"%d+") else parent=sgNames[parent] or '1' end end if sgval~='H' then if not parent or tonumber(parent) > tonumber(sgval) then if sgval=="1" then parent="0" else parent="1" end end end local itemTab, line, filename result[sgval]={} result[sgval].shapeType=sgval line,filename=extractItem(args['sga'..sgval] or '','image:') if sgval=='1' and not args.sga1 then line='circle,12px,blue,0deg' end -- ensure there is a parent=1 sga result[sgval].shapeFile=filename or '' -- sga= Attributes for shape itemTab=splitItem(line,4) result[sgval].shape = itemTab[1] or result[parent].shape result[sgval].shapeSize=itemTab[2] or result[parent].shapeSize result[sgval].shapeColor=itemTab[3] or result[parent].shapeColor result[sgval].shapeAngle=itemCheck(itemTab[4],'deg') or result[parent].shapeAngle -- sgb= Border outline attributes for shape itemTab=splitItem(args['sgb'..sgval],3) result[sgval].outlineWidth=itemCheck(itemTab[1],'px') or result[parent].outlineWidth result[sgval].outlineColor=itemTab[2] or result[parent].outlineColor result[sgval].outlineStyle=itemTab[3] or result[parent].outlineStyle --sgc=character attributes for label itemTab=splitItem(args['sgc'..sgval],4) result[sgval].textSZ=itemCheck(itemTab[1],'px') or result[parent].textSZ -- size of text in px result[sgval].textCL=itemTab[2] or result[parent].textCL -- colour for text result[sgval].textNG=itemCheck(itemTab[3],'deg') or result[parent].textNG -- Angle for text result[sgval].textAT=itemTab[4] or result[parent].textAT -- attributes bold, and/or italic --sgd=dotTag attributes itemTab=splitItem(args['sgd'..sgval],4) result[sgval].tagSize=itemCheck(itemTab[1],'px') or result[parent].tagSize result[sgval].tagColor=itemTab[2] or result[parent].tagColor result[sgval].tagSpacer=itemCheck(itemTab[3],'px') or result[parent].tagSpacer result[sgval].tagAngle=itemCheck(itemTab[4],'deg') or result[parent].tagAngle --sge= extension line attributes itemTab=splitItem(args['sge'..sgval],4) result[sgval].textEW=itemCheck(itemTab[1],'px') or result[parent].textEW -- width result[sgval].textEC=itemTab[2] or result[parent].textEC -- colour result[sgval].textES=itemTab[3] or result[parent].textES -- style --sgf= fx for label text itemTab=splitItem(args['sgf'..sgval],4) result[sgval].textSP=itemCheck(itemTab[1],'px') or result[parent].textSP -- spacing value for letters result[sgval].textLH=itemCheck(itemTab[2],'%') or result[parent].textLH -- Angle for text result[sgval].textOL=itemCheck(itemTab[3],'px') or result[parent].textOL -- width of text-border line result[sgval].textBG=itemTab[4] or result[parent].textBG -- color for text background return result end local function round(x,dec) -- x=number [, dec=integer] returns numeric value with upto dec decimals (all but first trailing zeros get truncated) if (dec or 0)==0 then return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5) --this avoids .0 where dec=0 end dec =10^(dec) return x>=0 and math.floor(x*dec+0.5)/dec or math.ceil(x*dec-0.5)/dec end local function maptogrid(t,r,test) --[[ converts mercator projection longitude and latitude coordinates to x and y pixel coordinates, within a frame of given size, centre coordinates and zoom level. t is a table of named indices: {lon, lat, lonbase, latbase, width, height, zoom} output is two values, x and y, rounded to r decimal places--]] local x,y x=t.width/2 + ( ((math.rad(t.lon)*6378137) - (math.rad(t.lonbase)* 6378137) ) / (156543.03*math.cos(t.latbase/180)/(2^t.zoom) ) )*(1-(0.075*(math.abs(t.latbase)/90))) y=t.height/2 + ( ( (math.log(math.tan(math.rad(t.latbase)/2+math.pi/4))*6378137) - (math.log(math.tan(math.rad(t.lat)/2+math.pi/4))*6378137) ) / (156543.03*math.cos(t.latbase/180) / (2^t.zoom) ) )*(1-(0.075*(math.abs(t.latbase)/90))) return round(x,r),round(y,r) --source: python code at https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames and https://wiki.openstreetmap.org/wiki/Mercator --[[ width and height are the size, in pixels, of the map, which will be centerd around lonbase,latbase. Method: Convert lon and lonbase to meter-offsets from coord(0,0), and subtract lonbase from lon, zoom and latbase are used to scale the resulting meter-offset to pixels, and add it to width/2. Convert lat and lat-base to meter-offsets from coord(0,0), subtract lat from latbase, scale the resulting meter-offset to pixels, add it to height/2. A correction factor '*(1-(0.075*(math.abs(t.latbase)/90) ) )' compensates for an error that seems to creep in towards the edges of the map at higher latitudes. It was identified experimentally, and ensures a dot at the edge is in the same place as if that location is positioned at the centre. Original Python code for lat,lon to x,y where 0,0 is the centre of the map print('x=',width+(((math.radians(lon1) * 6378137)- (math.radians(lonbase) * 6378137))/ (156543.03*math.cos(latbase/180)/(2**zoom))),' y=',height+(( (math.log(math.tan(math.pi / 4 + math.radians(latbase) / 2)) * 6378137)- (math.log(math.tan(math.pi / 4 + math.radians(lat1) / 2)) * 6378137))/ (156543.03*math.cos(latbase/180)/(2**zoom))) ) --]] end local function getScale(zoom, lat,magVal) if magVal and magVal>1 and magVal <=2 then zoom=zoom+(magVal-1) end local dist=(156543.03 * math.cos(math.rad(math.abs(lat))) / (2 ^ zoom))/17 local y if dist < 1 then y=(round(dist*10,1)) return tostring(y*100)..metres, tostring(round(y*109,0))..yards elseif dist <18 then y=(round(dist,0)) return tostring(y)..km, tostring(round(y*0.621371,1))..miles elseif dist <500 then y=round(dist/10) return tostring(y*10)..km, tostring(round(y*6.21371,0))..miles else y=round(dist/100) return tostring(y*100)..km, tostring(round(y*62.1371,0))..miles end end local function ParseData (args,dotval) -- for use with compressed, comma-separated 'sg plus dots' parameters -- takes a structured series of comma-separated items which get parsed as the following: -- dot(n)= (sgNumber or Name),{{coord}} or (lat,lon), (dotTag) -- dotlink(n) = single-parameter text to give wikilink and/or title used by tootlip, fullscreen dots and autocaption list -- dotlabel(n) = 'label text',pos(left,roght,top,bottom,centre,auto),(dx), (dy) pixel offsets, params, info -- dotpic(n) = single parameter wikimedia filename for an image to use in photopanel and/or fullscreen dots -- dotfeature(n)= 'mark-line' (,linewidth,style,gap) or 'photo-panel' (,image-dim,panel-width,panel-height), draws line to n-1 -- label is used if either a position and/or an x,y offset are not 0,0 ( if no label then dotTag will be put at at the x,y offset, or over the dot -- label text can be autoaligned if x,y puts it left or right of the dot, or centered if above/below) -- quote marks are not needed unless including commas within the label text -- param1 is optional items separated by spaces, and can include [nolabel nolist nomap hemisphere+1 hemisphere-1] -- info is free wikitext, to be used in the fullscreen dot box. (use dotpic to show a picture) --<!--| dotx=shape-group,[lat,lon or {{coord}} ], dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> local result={} local count=1 local row = convertCoords (args["dot"..dotval]) -- swap in any {{coord}} values so they are csv lat and lon row=fillCommas(row,4) result.code=dotval -- store the parameter name as the id code for item in string.gmatch(row,"[^,]+") do -- iterate through 'row', adding each csv item in turn, if present if count==1 then --see if it is a number or a name local pos= string.find(item,"%d+") if pos == 1 then result.group=string.lower(string.gsub(item,"%s+","")) else item=string.match(item,"(%w+)(.*)") -- ensure just a single word result.group=sgNames[item] end elseif count==2 then result.lat=tonumber(string.match(item,"[%.%-?%d]+")) or 0-- find the number, with no non-numeric stuff elseif count==3 then result.lon=tonumber(string.match(item,"[%.%-?%d]+")) or 0 elseif count==4 then result.dotTag=item:match( "^%s*(.-)%s*$" ) or "" -- dotTag allows for internal spaces, but no commas end count=count+1 end row, result.labelText= extractItem(args["dotlabel"..dotval]) result.labelText= string.gsub(result.labelText,"[%^]+","<br>") -- convert hats to line breaks local item=splitItem(row,6) result.labelPos=item[2] or 'center' result.dx=tonumber(string.match(item[3] or '0',"[%.%-?%d]+")) or 0 result.dy=tonumber(string.match(item[4] or '0',"[%.%-?%d]+")) or 0 result.param1=string.lower(item[5] or '') if string.find(result.param1,"hemisphere-1",1,true) then result.lon=result.lon-360 elseif string.find(result.param1,"hemisphere+1",1,true) then result.lon=result.lon+360 end local txt = '' if item[6] then -- ensure all info elements are included, including commas count=1 local max=6 for t1 in string.gmatch(fillCommas(row,max),"[^,]+") do if count>max then txt=txt..',' end if count>=max then txt=txt..t1 end count=count+1 end end result.info=txt result.imageName = args['dotpic'..dotval] -- Get first wikilinked item (if any) from the args.dotlink and set this plus the delinked text local testx=args["dotlink"..dotval] or '' result.dotLink=testx if testx ~= '' then testx=stripdivs(testx) result.title=delink({ testx }) local linkstart= string.find(testx,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then result.dlink=delink( { string.sub(testx,linkstart,string.find(testx,']]',1,true)+1),wikilinks='target' } ) else result.dlink='' end else result.dlink='' result.title='' end if args['dotfeature'..dotval] then local item=splitItem(args['dotfeature'..dotval],6) if (item[1] or '') =='photo-panel' then result.ppwidth= tonumber(string.match((item[3] or '110'),"%d+")) -- panel width result.ppheight= tonumber(string.match((item[4] or '48'),"%d+") ) --panel height result.photowidth=round(tonumber(string.match((item[2] or '1.3'),"[%.%-?%d]+")) * result.ppheight+1,0) -- dimension to set image size result.photoImage=result.imageName result.posType='photo-panel' elseif (item[1] or '') == 'mark-line' then local x=tonumber(dotval or '0') result.markDest=item[5] or tostring(x-1) result.mlWidth= tonumber(string.match((item[2]) or '',"%d+") or '1') result.mlStyle= item[3] or 'solid' result.mlGap=tonumber(string.match((item[4] or ''),"[%d]+") or '0') result.posType='mark-line' end --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end maplist.lon=result.lon maplist.lat=result.lat result.gridx, result.gridy = maptogrid(maplist,1) -- convert geo coords to grid xy - using values from maplist table return result end local function multiCheck (args, argName, argVal, defVal, alt) if not alt then alt='nonexistant' end if argVal=='H' and not args[argName..'H'] then argVal=highlightNum or '1' end if argVal=='' then return (args[argName] or args[alt] or (args[argName..'D']) or args[alt..'D'] or defVal) -- unnumbered args do not inherit from D or 1 else return (args[argName..argVal]) or (args[alt..argVal]) or (args[argName..'D']) or (args[alt..'D']) or (args[argName..'1']) or (args[alt..'1']) or defVal end end local function htmlTableLine (htmlTable,h1,h2,h3,h4,h5,type) if type == 'header' then if (h4 or '') ~= 'no-year' then h4='<th class="unsortable">'..h4 or ''..'</th>' else h4 = '' end if (h5 or '') ~= '' then h5='<th class="unsortable">'..h5..'</th>' else h5 = '' end return htmlTable..'<tr><th>'..(h1 or '')..'</th><th>'..(h2 or '')..'</th><th>'..(h3 or '')..'</th>'..h4..h5..'</tr>' else if (h4 or '') ~= 'no-year' then h4='<td>'..h4 or ''..'</td>' else h4 = '' end if (h5 or '') ~= '' then h5='<td>'..h5..'</td>' else h5 = '' end return htmlTable..'<tr><td>'..(h1 or '')..'</td><td>'..(h2 or '')..'</td><td>'..(h3 or '')..'</td>'..h4..h5..'</tr>' end end local function formatInt(number) if number == 0 then return notFound end local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)') int = int:reverse():gsub("(%d%d%d)", "%1,") return minus .. int:reverse():gsub("^,", "") .. fraction end local function convertToSqKm (value,punit) if string.find(punit, 'Q35852') then value = value / 100 -- convert to sq km if it's hectares elseif string.find(punit, 'Q232291') then value = value /(1/2.59) -- or this if it's square miles elseif string.find(punit, 'Q81292') then value = value / 247.1 -- or this if it's acres end return value end local function assignTradstyleShape(shapeResult,default,dotResult,args,nval) local item,itemTab local autoDotTag='' local shapeWidth,shapeHeight=0,0 local argval=nval -- to catch the unnumbered shape series local qvalue='' if argval=='0' then argval='' end if nval=='H' then shapeResult.H={} end -- Assign autovalues if a wikidata Qvalue has been supplied -- some preliminary documentation -- geo-data-type= default is P1082 (population) -- other options might include number of households (P1538) and per capita income (P10622) or land area (P2046) -- geo-data-list= this is a comma-separated list of Q values, for a manually generated list that doesn't require nymbered parameters -- geo-scalefactor = can enlarge or shrink all the dots in proportion, depending an what the map needs -- geo-region = needs to be a Qvalue of the larger administrative region that the settlements/sub-regions are in -- geo-sub-region needs to be a Pvalue, but will default to P150 (some pages use 'has parts' = P527) -- geo-data3= etc numbered Qvalue item, if used in conjunction with other shape/label parameters -- shapeD= sets default shape, which migh be n-circle to include numbers. -- geo-number-size= will give a fixed size for shape numbers. (defaults to 12 when using proportional dots) -- geo-boundaries= if set to 1 will add any Qvalues for the sub-regions to 'map-data-light'. -- show-geo-dots= to use proportional dots. Will be fixed size if 0. (set mark-sizeD=0 to have no dot at all, eg to show labels or boundaries) -- find-geo-dots=1 or 0 (needs to also include geo-region) -- label-posD = gets additional options: [off] gives no label, [on] will position lower centre, -- [on+] includes numeric values beside/below label, [on^] will always put it below. [on-] always puts values beside if args['geo-data'..argval] then -- and (args['mark-title'..argval] or '') ~= 'none' then local pvalue = args['geo-data-type'] or 'P1082' local plabel = mw.wikibase.getLabel(pvalue) if not args['geo-number-size'] then args['geo-number-size']=12 end if not args['label-posD'] then args['label-posD'] = 'on' end qvalue=args['geo-data'..argval] -- total poulation is a Qvalue wikidata page entry, or add up all the geo-data values or use a random 10000 local region = args['geo-region'] -- or mw.wikibase.getEntityIdForCurrentPage() local thisPop=0 local areatxt='' local yearVal='' if mw.wikibase.getBestStatements(qvalue,pvalue)[1] then thisPop=tonumber(mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.amount) or 0 if pvalue == 'P2046' then -- area, so convert from ha to sq km if needed (/100) thisPop = convertToSqKm(thisPop, mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.unit) end if mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers and mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers.P585 then yearVal = mw.wikibase.getBestStatements(qvalue,pvalue)[1].qualifiers.P585[1].datavalue.value.time if yearVal then yearVal = yearVal:sub(2,5) end -- wikidata reports an entire date and time, so extract the year end else debugmsg(mw.addWarning(mw.wikibase.getLabel(qvalue)..' ('..qvalue..') has no recorded '..plabel)) end local scalefactor=args['geo-scalefactor'] or 1 if argval=='1' and args['mark-size1'] and not args['mark-sizeD'] then args['mark-sizeD'] = args['mark-size1'] end if argval=='1' and args['label-pos1'] and not args['label-posD'] then args['label-posD'] = args['label-pos1'] end if not args['mark-size'..argval] then if args['show-geo-dots'] == '0' then args['mark-size'..argval] = args['mark-sizeD'] or '1' -- just use a 'point' for the label to attach to else if thisPop>0 then args['mark-size'..argval] = round(math.sqrt(thisPop/geoTotalPop*100) * 8 * scalefactor,0) else args['mark-size'..argval] = args['mark-sizeD'] or '1' args['shape-outline'..argval]=args['shape-outline'..argval] or args['shape-outlineH'] end end end local posTemp=string.lower(multiCheck(args,'label-pos',argval,'on')) args['label-pos'..argval] = posTemp posTemp=splitItem(posTemp,2)[1] if pvalue == 'P2046' then thisPop = round(thisPop,2) areatxt=sqkm..areatxt -- round decimals if sq km area elseif pvalue == 'P10622' then if mw.wikibase.getBestStatements(qvalue,pvalue)[1] then local punit=(mw.wikibase.getBestStatements(qvalue,pvalue)[1].mainsnak.datavalue.value.unit or ''):match("Q.*") -- extract Q value if punit == 'Q4917' then areatxt=' US$' elseif punit == 'Q4916' then areatxt=' euro' elseif punit == 'Q25224' then areatxt=' £GBP' else areatxt=mw.wikibase.getLabel(punit)..areatxt end end end if posTemp=='off' then else if not args['label'..argval] then args['label'..argval] = mw.wikibase.getLabel(qvalue) end local tline=posTemp:find("[%+%^%-]") if tline then local tval=posTemp:sub(tline,tline) local eline posTemp=posTemp:sub(1,tline-1) if posTemp == 'left' or posTemp == 'right' or string.find(posTemp,'east') or string.find(posTemp,'west') then if tval == '^' then eline = '^' else eline = ' ' end -- only add line break if 'forced', used for left/right elseif tval == '+' or tval == '^' then eline = '^' -- add linebreak unless 'forced' not to else eline = ' ' -- must be a dash, so no line break end args['label'..argval] = args['label'..argval]..eline..'('..formatInt(thisPop)..')' args['label-pos'..argval] = posTemp..args['label-pos'..argval]:sub(tline+1,#args['label-pos'..argval]) end end if posTemp=='on' then if argval=='1' and (not args.ldyD) then args.ldyD = args['ldy'..argval] or '0' end if not args['ldy'..argval] then args['ldy'..argval] = args.ldyD end -- ensure ldy for that dot is added to the position args['label-pos'..argval]='center' local _,br = string.gsub(args['label'..argval],'%^','') local item=splitItem(args['label-size'..argval],2) args['ldy'..argval] = (args['ldy'..argval] or 0) + ((item[1] or 11) * ((br+1)*0.6)) + ((args['geo-number-size'] or 12) / 2) end local nt='' if args['mark-title'..argval] == 'nolist' then nt='nolist,' end if not args['mark-title'..argval] or args['mark-title'..argval] == '' or args['mark-title'..argval] == 'nolist' then if mw.wikibase.getSitelink(qvalue) then args['mark-title'..argval] = nt..'[['..mw.wikibase.getSitelink(qvalue)..']]' else args['mark-title'..argval] = nt..mw.wikibase.getLabel(qvalue) end end -- If mark-title is not set locally then attempt Sitelink value, or use label if thisPop == 0 then areatxt='' end captionTableText[argval]={} captionTableText[argval][1]= args['mark-title'..argval] captionTableText[argval][2]= formatInt(thisPop) if maplist.width >= 280 then captionTableText[argval][3]= yearVal else captionTableText[argval][3]='no-year' end if args['show-q-values'] == '1' then captionTableText[argval][4]='<small>[[wikidata:'..qvalue..'|'..qvalue..']]</small>' else local nbsp = '&nbsp;' if maplist.width < 230 then nbsp = ' ' end captionTableText[argval][4]='' if captionTableText[argval][3] == 'no-year' then captionTableText[argval][2] = captionTableText[argval][2]..'<small><sup>'..nbsp..'[[wikidata:'..qvalue..'|WD]]</sup></small>' else captionTableText[argval][3] = captionTableText[argval][3]..'<small><sup>'..nbsp..'[[wikidata:'..qvalue..'|WD]]</sup></small>' end end if args['mark-title'..argval] == 'none' then -- no title else args['mark-title'..argval] = args['mark-title'..argval]..' ('..plabel..' '..formatInt(thisPop)..areatxt..')' end if not args['mark-description'..argval] then args['mark-description'..argval] = (mw.wikibase.getDescription(qvalue) or '')..'<br>' end if (not args['mark-image'..argval]) and (mw.wikibase.getBestStatements(qvalue,'P18')[1]) then args['mark-image'..argval] = mw.wikibase.getBestStatements(qvalue,'P18')[1].mainsnak.datavalue.value end item = multiCheck(args,'shape',argval,'circle') --default to circle if geo-data if not string.find(item,'svgpath,') then item=string.lower(item) end -- ensure svgpath items retain original case values else item = multiCheck(args,'shape',argval,'image') if not string.find(item,'svgpath,') then item=string.lower(item) end -- ensure svgpath items retain original case values end if string.find(item,'n-',0,true)==1 or string.find(item,'l-',0,true)==1 then autoDotTag=string.sub(item,0,1) item=string.sub(item,3) end if item == 'image' then shapeResult[nval].shape = 'image:' shapeResult[nval].shapeFile =multiCheck(args,'mark',argval,'Red pog.svg') shapeWidth=-1 else shapeResult[nval].shape = item end item= multiCheck(args,'mark-size',argval,(shapeResult[nval].shapeSize or '14px')) -- add geo-data as fallback if already assigned local a,b,c= getsize(string.gsub(string.gsub(item,',','px')..'px','pxpx','px')) if b==a and args['mark-dim'..argval] then b= b / tonumber(string.match(args['mark-dim'..argval],"[%.%-?%d]+")) end shapeHeight=b/2 item=tostring(a)..'px'..tostring(b)..'px'..tostring(c)..'px' shapeResult[nval].shapeSize= item itemTab=splitItem(multiCheck(args,'shape-color',argval,'hard red'),2) shapeResult[nval].shapeColor=itemTab[1] or 'hardred' item=itemCheck(itemTab[2],'%') -- jump through the various opacity hoops and add to color if needed if not item then item=itemCheck(args['shape-opacity'..argval],'%') end if item and item~='0%' and item~='100%' then shapeResult[nval].shapeColor=shapeResult[nval].shapeColor..item end shapeResult[nval].shapeAngle=itemCheck(multiCheck(args,'shape-angle',argval,'0'),'deg') or '0deg' if args['shape-angle'..argval] then shapeResult[nval].shapeAngleArg=true else shapeResult[nval].shapeAngleArg=false end --sort out the outline entry itemTab=splitItem(multiCheck(args,'shape-outline',argval,'transparent,0,100,solid'),4) shapeResult[nval].outlineColor=itemTab[1] or 'dark grey' shapeResult[nval].outlineWidth=itemCheck(itemTab[2],'px') or '1px' if itemTab[3] and itemTab[3]~='100' and itemTab[3]~='0' then shapeResult[nval].outlineColor=shapeResult[nval].outlineColor..itemCheck(itemTab[3],'%') end shapeResult[nval].outlineStyle=itemTab[4] or 'solid' -- label size, background, outline shapeResult[nval].textAT=args['label-attribute'..argval] or '' itemTab=splitItem( multiCheck(args,'label-size',argval,'12'),3) shapeResult[nval].textSZ=itemCheck(itemTab[1],'px') or '12px' if itemTab[2]=='outline' then shapeResult[nval].textBG=itemTab[3] or 'transparent' shapeResult[nval].textOL='1px' elseif itemTab[3]=='outline' then shapeResult[nval].textBG=itemTab[2] or 'transparent' shapeResult[nval].textOL='1px' else shapeResult[nval].textOL='0px' shapeResult[nval].textBG=itemTab[2] or 'transparent' end if getColor(shapeResult[nval].textBG)==CTB['hardgrey'] and shapeResult[nval].textBG~='hardgrey' then shapeResult[nval].textBG= 'transparent' end --label color etc itemTab=splitItem(multiCheck(args,'label-color',argval, 'darkgrey','label-colour'),2) shapeResult[nval].textCL=itemTab[1] or 'darkgrey' if itemTab[2] and itemTab[2]~='0%' and itemTab[2]~='100%' then shapeResult[nval].textCL=shapeResult[nval].textCL..itemTab[2] end shapeResult[nval].textSP=itemCheck( multiCheck(args,'label-spacing',argval,'0'),'px') -- sets letter-spacing in px shapeResult[nval].textLH=itemCheck( multiCheck(args,'label-height',argval,'120'),'%') -- sets line height, 120% default shapeResult[nval].textNG=itemCheck(multiCheck(args,'label-angle',argval,'0'),'deg') if args['label-angle'..argval] then shapeResult[nval].textNGarg=true else shapeResult[nval].textNGarg=false end --sgd=dotTag attributes shapeResult[nval].tagSize=(args['geo-number-size'] or tostring(shapeHeight*1.5))..'px' local c1,c2=checkColors(shapeResult[nval].shapeColor) shapeResult[nval].tagColor=c2 shapeResult[nval].tagSpacer='0px' shapeResult[nval].tagAngle='0deg' -- sge extension line attributes local shapePos=splitItem(multiCheck(args,'label-pos',argval,'right'),6) if shapePos[2]=='with-line' or shapePos[2]=='n-line' then shapeResult[nval].textEW=(shapePos[3] or '1')..'px' -- width shapeResult[nval].textEC=(shapePos[4] or shapeResult[nval].shapeColor or 'darkgrey') elseif shapePos[2]=='photo-panel' then shapeResult[nval].textEW='2px' -- width shapeResult[nval].textEC=shapeResult[nval].textCL else shapeResult[nval].textEW='0px' -- width shapeResult[nval].textEC='grey'-- colour end shapeResult[nval].textES='solid' if argval=='H' then return dotResult end --Assign dot values local dotItem={} dotItem.group=nval dotItem.code=nval dotItem.posType=shapePos[2] or 'nil' if (shapePos[2] or '') =='photo-panel' then dotItem.ppwidth= tonumber(string.match((shapePos[4] or '110'),"%d+")) dotItem.ppheight= tonumber(string.match((shapePos[5] or '48'),"%d+") ) dotItem.photowidth=round(tonumber(string.match((shapePos[3] or '1.3'),"[%.%-?%d]+")) * dotItem.ppheight+1,0) dotItem.photoImage=args['mark-image'..argval] --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end if (shapePos[2] or '') =='mark-line' then local x=tonumber(argval or '1') dotItem.markDest=shapePos[6] or tostring(x-1) dotItem.mlWidth= tonumber(string.match((shapePos[3] or '1'),"%d+")) dotItem.mlStyle= shapePos[4] or 'solid' dotItem.mlGap=tonumber(string.match((shapePos[5] or '0'),"[%d]+")) shapeResult[nval].textEC=shapeResult[nval].outlineColor or 'darkgrey' end if args['mark-coord'..argval] then itemTab=splitItem(convertCoordsTrad (args['mark-coord'..argval]),2) if itemTab[1] == nil or itemTab[2] == nil then debugmsg(mw.addWarning('Unable to read coordinates for mark-coord'..argval)) else dotItem.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) or 0 end else dotItem.lat=tonumber(string.match(args['mark-lat'..argval],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(args['mark-lon'..argval],"[%.%-?%d]+")) or 0 end if args['dateline'..argval] and (args['dateline'..argval]=='1' or args['dateline'..argval]=='-1') then dotItem.lon=dotItem.lon+(tonumber(args['dateline'..argval] ) *360) end maplist.lon=dotItem.lon maplist.lat=dotItem.lat dotItem.gridx, dotItem.gridy = maptogrid(maplist,1) local item=args['mark-title'..argval] or '' -- sort out the caption, wikilink and plaintext tooltip items from dotLink if item=='none' then dotItem.param1='nomap nolist' item='' end if string.find(item,'nolist') then dotItem.param1='nolist,' item = string.sub(args['mark-title'..argval],8) or args['label'..argval] end dotItem.dotLink=item if item ~= '' then item=stripdivs(item) dotItem.title=delink({item}) local linkstart= string.find(item,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then dotItem.dlink=delink({string.sub(item,linkstart,string.find(item,']]',1,true)+1),wikilinks='target'}) -- debugmsg('dlink for '..item..' is '..dotItem.dlink) else dotItem.dlink='' end else dotItem.dlink='' dotItem.title='' end if autoDotTag=='n' then item=nval elseif autoDotTag=='l' then item=string.char(64+tonumber(nval)) else item='' end dotItem.dotTag = args['numbered'..argval] or item if shapePos[2]=='n-line' and (args['label'..argval] or args['label'..argval]=='') then if dotItem.dlink =='' then item=dotItem.dotTag..' '..args['label'..argval] else item='[['..dotItem.dlink..'|'..dotItem.dotTag..']] '..args['label'..argval] end else item=(args['label'..argval] or '') end if args['labela'..argval] then item = item..'^'..args['labela'..argval] end if args['labelb'..argval] then item = item..'^'..args['labelb'..argval] end local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end dotItem.labelText = a -- convert hats to line breaks if argval=='' then item = (args['label-offset-x']) or (args.ldx) or '0' else item=args['label-offset-x'..argval] or args['ldx'..argval] or args['label-offset-xD'] or args.ldxD or args['label-offset-x1'] or args.ldx1 or '0' end dotItem.dx=tonumber(string.match(item,"[%.%-?%d]+")) if argval=='' then item = (args['label-offset-y']) or (args.ldy) or '0' else item=args['label-offset-y'..argval] or args['ldy'..argval] or args['label-offset-yD'] or args.ldyD or args['label-offset-y1'] or args.ldy1 or '0' end dotItem.dy=tonumber(string.match(item,"[%.%-?%d]+")) dotItem.labelPos=shapePos[1] if args['mark-image'..argval] then dotItem.imageName= args['mark-image'..argval] end dotItem.info=args['mark-description'..argval] or '' table.insert(dotResult,1,dotItem) -- add to start of list, so they are in reverese order for displaying -- debugmsg(dotItem.code..' = '..dotItem.lat..','..dotItem.lon..': '..dotItem.labelText..' - '..dotItem.title..', '..shapeResult[nval].shapeSize) return dotResult end local function tradstyleParseShapes(args,dotTable,dotmax) local sgNumbers,sgSortable={},{} for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['geo-data'..x] and (not args['mark-coord'..x]) and (not args['mark-lat'..x]) then -- get lat and lon values from wikidata and put them into the args.lat and args.lon for use wherever needed if (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1]) then args['mark-lat'..x] = (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1].mainsnak.datavalue.value.latitude) or '0' args['mark-lon'..x] = (mw.wikibase.getBestStatements(args['geo-data'..x],'P625')[1].mainsnak.datavalue.value.longitude) or '0' local p=args['geo-data-type'] or 'P1082' if mw.wikibase.getBestStatements(args['geo-data'..x],p)[1] then local n = tonumber(mw.wikibase.getBestStatements(args['geo-data'..x],p)[1].mainsnak.datavalue.value.amount) or 0 if p == 'P2046' then -- area, so convert to sq km if needed (/100) n = convertToSqKm( n, mw.wikibase.getBestStatements(args['geo-data'..x],p)[1].mainsnak.datavalue.value.unit) end geoTotalPop = geoTotalPop + n end else debugmsg(mw.addWarning('could not find coords for '..mw.wikibase.getLabel(args['geo-data'..x])..', '..args['geo-data'..x])) end end if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) then sgNumbers[x]=x -- add it to the list end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form (needs a second table, seemingly) if args['mark-coord'] or (args['mark-lat'] and args['mark-lon']) then table.insert(sgSortable,'0') end -- add it to the end of the list local default={} default.D={} local dotResult={} for k,sgnum in pairs(sgSortable) do -- work through the sorted list, parsing each set of shapes in turn, from 1 upwards shapeList[sgnum]={} dotTable=assignTradstyleShape(shapeList,default,dotTable,args,sgnum) end dotTable=assignTradstyleShape(shapeList,default,dotTable,args,'H') -- construct an extra highlight shapeitem local unitConvert=1 local areaUnits='' if args['geo-region'] and (args['find-geo-dots'] or '1') ~= '0' then local region = args['geo-region'] -- or mw.wikibase.getEntityIdForCurrentPage() local pvalue = args['geo-data-type'] or 'P1082' local plabel = mw.wikibase.getLabel(pvalue) local subhead='' -- Assemble the subhead content, a line of text about the region as a whole if region and mw.wikibase.getBestStatements(region,pvalue)[1] then local punit=mw.wikibase.getBestStatements(region,pvalue)[1].mainsnak.datavalue.value.unit:match("Q.*") -- extract Q value if pvalue == 'P10622' then -- add other currency-based pvalues here areaUnits=mw.wikibase.getLabel(punit) end if punit == 'Q35852' then unitConvert=100 areaUnits = ' '..sqkm -- if its hectares, convert to sq km elseif punit == 'Q232291' then unitConvert = (1/2.59) areaUnits = ' '..sqkm -- or this if it's square miles elseif punit == 'Q81292' then unitConvert = 247.1 areaUnits = ' '..sqkm -- or this if it's acres elseif punit == 'Q712226' then unitConvert = 1 areaUnits = ' '..sqkm -- or this if it's already sq km elseif punit == 'Q4917' then areaUnits=' US$' -- replace long version with some shortform currencies () elseif punit == 'Q4916' then areaUnits=' euro' elseif punit == 'Q25224' then areaUnits=' £GBP' end -- if pvalue == 'P2046' then -- may need to add other instances of sq km so add in other P-items here if they need units showing -- end geoTotalPop=(mw.wikibase.getBestStatements(region,pvalue)[1].mainsnak.datavalue.value.amount) / unitConvert local yearVal if mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers then yearVal = mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P585[1].datavalue.value.time if yearVal and yearVal:sub(1,1) == '+' then yearVal = yearVal:sub(2,5) if mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459 and mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459[1] then local methodVal = mw.wikibase.getBestStatements(region,pvalue)[1].qualifiers.P459[1].datavalue.value.id if methodVal then yearVal=yearVal..' '..mw.wikibase.getLabel(methodVal) end end yearVal = ' ('..yearVal..')' else yearVal ='' end end subhead=totalItems..' '..mw.wikibase.getLabel(region)..' '..plabel..' ([[wikidata:'..region..']]) = '..formatInt(round(geoTotalPop),1)..areaUnits..yearVal else if pvalue == 'P2046' then areaUnits = sqkm end subhead=totalItems..' '..mw.wikibase.getLabel(region)..' '..plabel..' ('..sumOfDatapoints..') = '..formatInt(round(geoTotalPop),1)..areaUnits end if maplist.width < 350 and #plabel > 4 then plabel=string.sub((plabel or ' '),1,3)..'.' end if maplist.width < 330 then numberCol = ' ' end if maplist.width < 280 then year = 'no-year' end captionTable = subhead if (args['show-q-values'] or '') ~= '1' then captionTable = captionTable..'<div style="float:right;"><small> (WD&nbsp;=&nbsp;'..wikidataLink..')</small></div><br>' wikidataLink='' end captionTable = captionTable..'<table class="wikitable plainrowheaders sortable mw-collapsible mw-collapsed" style="width: 100%;"><caption>'..(args.toggletext or toggletext)..' </caption>' captionTable = htmlTableLine(captionTable,numberCol,mw.wikibase.getLabel(region),plabel..' '..areaUnits, year, wikidataLink,'header') end return dotTable end local function checkfortooltip (title,dx,dy,dotlabel,dlink,nolabel) -- returns tlink if available, and dlink, if needed and tshape=true if shape needed local tshape,tlink = false,"" if dlink~='' and not nolabel then tlink=dlink end if (tlink=="" or nolabel) and title~="" then tshape=true end -- tshape flags True if title is wanted for shape if (not (dx==0 or dy==0) or dotlabel=='') and title~='' then tshape=true end -- add tooltip to shape if its number has moved return tshape,tlink end local function tshift(angle) -- adjustment to place text near the centre of a triangle, shifted to allow rotation of triangle shape local x=tonumber(string.match(angle,"%-?%d+")) if x<0 then x=360+x end -- set to a single degree direction, 0 to 360 if x>359 then return 0,0 end -- shift the centre of the triangle based on rotation value if x <=40 or x>=320 then return -0.17,0 -- triangle up= -shiftv elseif x>=140 and x<=220 then return 0.17,0 --triangle down= +shiftv elseif x >220 then return 0,-0.17 --triangle left= -shifth elseif x >40 then return 0,0.22 --triangle right= +shifth end return 0,0 end local function makeTriangle(result,row,shape,outline,tlink) local w,h,r=getsize(shape.shapeSize) if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) w=w+p*2 h=h+p*2 end table.insert(result,'<div ') if tlink then table.insert(result,' title="'..row.title..'" ') end table.insert(result,'style="display:inline-block; position: absolute') if shape.shapeAngle ~= '0deg' then table.insert(result,'; transform: rotate('..shape.shapeAngle..')') end local shiftv,shifth=0,0 shiftv,shifth=tshift(shape.shapeAngle) table.insert(result,'; top: '..tostring(row.gridy-h/2+h*shiftv)..'px') table.insert(result,'; left: '..tostring(row.gridx-w/2+w*shifth)..'px; width: 0; height: 0; outline-width: 0px') table.insert(result,'; border-left: '..tostring(w/2)..'px solid transparent') table.insert(result,'; border-right: '..tostring(w/2)..'px solid transparent') if outline then -- fill with outline colour, to make a 'base layer' or shape colour table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.outlineColor).. '">') else table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.shapeColor).. '">') end table.insert(result,'</div>') end local function makeSquare(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) if shape.shape=='square' then h=w end -- squares are always square!. box can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end if r~=0 then div:css('border-radius',tostring(r).."px") end if shape.shape=='panel' then div:css('top', tostring(row.gridy).."px") div:css('left', tostring(row.gridx).."px") else div:css('top', tostring(row.gridy-h/2).."px") div:css('left', tostring(row.gridx-w/2).."px") end div:css('width', tostring(w).."px") div:css('height', tostring(h).."px") div:css('background-color', getColor(shape.shapeColor) ) div:css('color', 'inherit') table.insert(result,tostring(div)) end local function makeCircle(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding if shape.shape=='circle' then h=w end -- circles are always round. ellipse can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end div :css('top', tostring(row.gridy-h/2).."px") :css('left', tostring(row.gridx-w/2).."px") :css('width', tostring(w).."px") :css('height', tostring(h).."px") :css('border-radius', "50%") :css('background-color', getColor(shape.shapeColor) ) :css('color', 'inherit') table.insert(result,tostring(div)) end local function makeRuleA(result,row,shape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local lineV=0 if shape.shape=='rulea' then lineV=oWid*3+16 end table.insert(result,"<div style=\"display:inline-block; position: absolute") -- create a square transparent container, which will rotate line and arrow together table.insert(result,"; top:"..tostring(row.gridy - w/2).."px") table.insert(result,"; left:"..tostring(row.gridx - w/2).."px") table.insert(result,"; width:"..tostring(w).."px") table.insert(result,"; height:"..tostring(w).."px; background:transparent; color:inherit;") table.insert(result,"; transform: rotate( "..tostring(tonumber(string.match(shape.shapeAngle,"[%.%-?%d]+")) - 90).."deg);\">" ) table.insert(result,"<div style=\"display:inline-block; position: absolute") -- put the line (as a border-right) across the container table.insert(result,"; top:"..tostring(lineV).."px") table.insert(result,"; left:"..tostring((w - oWid )/2).."px; width: 0px") table.insert(result,"; height: "..tostring(w -lineV).."px") table.insert(result,"; border-right: "..shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) table.insert(result,"; background:transparent; color:inherit;\"></div>") if shape.shape=='rulea' then table.insert(result,"<div style=\"display:inline-block; position: absolute; top: 0px") --and add arrow head table.insert(result,"; left:"..tostring(w/2-( oWid/2)-oWid*0.55-2).."px; width: 0; height: 0; outline-width: 0px") table.insert(result,"; border-left: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-right: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-bottom: "..tostring(oWid*3+16).."px solid "..getColor(shape.outlineColor).."\"></div>") end table.insert(result,"</div>") end local function makeCurveA(result,row,shape) -- draw a curve with Arrow ----- local w,h=getsize(shape.shapeSize) -- = width,height local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local Angle=tonumber(string.match(shape.shapeAngle,"[%.%d]+")) table.insert(result,'<div style="position: absolute;') --set up out div, which will allow the whole to rotate table.insert(result,'top:'..tostring(row.gridy - (w + oWid*3+16)/2)..'px;') table.insert(result,'left:'..tostring(row.gridx - ( w + oWid*3+16)/2)..'px; ') table.insert(result,'width: '..tostring(w+oWid*3+16)..'px; ') table.insert(result,'height: '..tostring(w+oWid*3+16)..'px; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate( '..tostring(Angle-120)..'deg);">') else table.insert(result,'transform: rotate( '..tostring(Angle -62)..'deg);">') end table.insert(result,'<div style="position: absolute;') --set up div for the rounded corner of a rectangle table.insert(result,'border-left: '..shape.outlineWidth..' '..shape.outlineStyle..' '..getColor(shape.outlineColor)..';') if shape.shape=='curvea' then table.insert(result,'border-radius: 10000px 0 0 '..tostring(w)..'px; top:0px; left:'..tostring(w*0.25)..'px;') else table.insert(result,'border-radius: '..tostring(w)..'px 0 0 10000px;') table.insert(result,'top:'..tostring((oWid*3+16)/2+w*0.15)..'px; left:'..tostring(w*0.25)..'px;') end -- and add a triangular arrow head table.insert(result,'width: '..tostring(w)..'px; height: '..tostring(w)..'px;"></div><div style="position: absolute; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate(180deg); top:'..tostring(w-1)..'px; ') else table.insert(result,'transform: rotate(0deg); top: '..tostring(0-( ( oWid*3+16)/2)+1+( w*0.15) )..'px;') end -- reverse table.insert(result,'left:'..tostring(0-( oWid*0.6)-2+(w*0.25))..'px;') table.insert(result,'width: 0; height: 0; outline-width: 0px; border-left: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-right: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-bottom: '..tostring(oWid*3+16)..'px solid '..getColor(shape.outlineColor)..';"></div></div>') end local function makeLineTo (result,x1,y1,x2,y2,oWid, oStyle, oCol,double,shape) table.insert(result,"<div style=\"display:inline-block; position: absolute;") -- draw a line between x1,y1 and x2,y2, px-coords where 0,0 is centre of frame -- Maths calculations thanks to ES local tw if double <= 1 then tw = tonumber(string.match(oWid,"[%.%d]+"))/2 -- shift centre by half the line width else tw = tonumber(string.match(oWid,"[%.%d]+")) + (double/2) - 0.5 -- shift centre by a line width + half the gap end -- debugmsg(oWid..'+'..double..'/2='..tw) table.insert(result,"left: "..tostring(x1+( (x2-x1)/2) - (math.sqrt( ( x2-x1)^2 + (y2-y1)^2 )/2) ).."px;") table.insert(result,"top: "..tostring(y1+( ( y2-y1 )/2) - tw).."px;") table.insert(result,"width: "..tostring(math.sqrt( (x2-x1 )^2 + ( y2-y1 )^2) ).."px;") table.insert(result,"height: "..tostring(double).."px; background-color:transparent; color:inherit; ") table.insert(result,"outline-width: 0; border-bottom: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) if double>1 then table.insert(result,"border-top: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) end local lineAngle=math.atan(( y2-y1)/( x2-x1 ) )*180/math.pi -- if x1 == x2 then lineAngle=90 end table.insert(result,"transform: rotate("..lineAngle.."deg);\"></div>") -- debugmsg('text angle started as '..shape.textNG..' and lineAngle = '..lineAngle) if (lineAngle < -90) or (lineAngle >90) then lineAngle=lineAngle-180 end if shape and not shape.textNGarg then shape.textNG=tostring(round(lineAngle,0))..'deg' end -- Put in line angle for a default text angle as well -- if (y2 >= y1) and (x2 <= x1) then lineAngle=lineAngle-180 debugmsg('arrow angle corrected') end if x2 < x1 then lineAngle=lineAngle-180 end if shape and not shape.shapeAngleArg and shape.shape == 'arrowhead' then shape.shapeAngle=tostring(round(lineAngle,0))..'deg' end -- line angle for arrowhead -- debugmsg('text angle is now '..shape.textNG..'with arrow at '..lineAngle) end local function makeClipPath(result,row,shape,outline,tshape) --tshape is a flag to show if the tooltip (title=) is wanted -- return the text css div code to position and draw a shape occupying a specified clippath if string.find(shape.shape, 'svgpath,') and #shape.shape > 9 then pathshape.svgpath=string.sub(shape.shape,9) shape.shape='svgpath' end if not pathshape[shape.shape] then debugmsg(mw.addWarning('shape'..row.code..' = '..shape.shape..'. The shape name is not defined')) return end local w,h,r=getsize(shape.shapeSize) if shape.shape == 'arrowhead' then -- enlarge arrowheads, as they only go to the middle of the notional shape-size w=w*2.5 h=h*2.5 end if string.match(shape.shape,"circle") or string.match(shape.shape,"square") then h=w end -- use ellipse and box for stretched shapes local shifth,shiftv = 0,0 if string.match(shape.shape,"triangle") then shiftv,shifth =tshift(shape.shapeAngle) end if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) or 0 w=w+p*2 h=h+p*2 end table.insert(result,"<div ") if tshape then -- Add tooltip if needed table.insert(result," title=\""..row.title.."\" ") end table.insert(result,"style=\"display:inline-block; position: absolute; background-color:") if outline then table.insert(result,getColor(shape.outlineColor)) -- fill with outline colour, to make a 'base layer' else table.insert(result,getColor(shape.shapeColor)) end table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;"..pathshape[shape.shape].."&#39;) ") -- adds the required clippath data from the table of pathshape string literals table.insert(result,"; top:"..tostring(row.gridy - 10 + h*shiftv).."px") table.insert(result,"; left:"..tostring(row.gridx - 10 + w*shifth).."px") table.insert(result,"; width:20px") -- needs to be a path within a 20px20px box, and then rescales using size values to match other shape sizes table.insert(result,"; height:20px; transform:") if shape.shapeAngle ~= "0deg" then table.insert(result," rotate("..shape.shapeAngle..")") end table.insert(result," scale("..tostring(w/16)..", "..tostring(h/16)..")") table.insert(result,"\"></div>") end local function makeImage(result,row,shape) local w,h,r=getsize(shape.shapeSize) local image=shape.shapeFile if not image or image=='' then image='Red pog.svg' end local imagediv=mw.html.create ('div') imagediv:css('position', "absolute") if shape.shapeAngle ~= "0deg" then imagediv:css('transform',"rotate("..shape.shapeAngle..")") end imagediv :css('top', (row.gridy+2 + math.min(h/2-12,0) - h/2).."px") --File seems to adjust pos for small images :css('left', (row.gridx-1-w/2).."px") :css('background-color', "transparent" ) :css('color','inherit') :wikitext('[[file:'..image..'|'..tostring(w+2)..'px|alt='..(row.title or '')..'|link=]]') table.insert(result,tostring(imagediv)) end local function makePhotoPanel(result,row,shape) local h=row.ppheight table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;' ) table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') table.insert(result, 'width: '..tostring(row.ppwidth)..'px; height: '..tostring(h)..'px; border-radius: 2px; color:inherit;') table.insert(result, 'background-color: #E8E8D6; outline: 2px solid '..getColor(shape.textCL)..'; box-shadow: 2px 2px 4px #33203335;"></div>') if row.photoImage and row.photowidth >0 then table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;') if row.labelPos=='left' or string.find(row.labelPos,'west') then table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') row.dx=row.dx+row.photowidth/2 else table.insert(result, 'left: '..tostring(row.gridx+row.dx + (row.ppwidth-row.photowidth) - row.ppwidth/2)..'px;') row.dx=row.dx-row.photowidth/2 end table.insert(result, 'background-color:transparent; color:inherit; border-radius: 2px;">') table.insert(result,'[[File:'..row.photoImage..'|x'..tostring(h)..'px|File:'..row.photoImage..']]</div>') row.labelPos='center' end end local function makePanelText(result, row, shape) local w,h,r=getsize(shape.shapeSize) local ty=tonumber(string.match(shape.textSZ or '11',"%d+") ) table.insert(result,'<div style="position:absolute; line-height: 120%; font-size: '..shape.textSZ..'; color:'..getColor(shape.textCL)..';') if row.labelPos == 'left' or string.find(row.labelPos,'west') then table.insert(result,'top: '..tostring(row.dy+row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+3)..'px; text-align: left; width:'..tostring(w)..'px;') elseif row.labelPos == 'right' or string.find(row.labelPos,'east') then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+w-3)..'px; text-align: right; width: max-content; transform: translateX(-100%);') elseif row.labelPos == 'top' or row.labelPos== 'north' then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') elseif row.labelPos == 'bottom' or row.labelPos=='south' then local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*1.1 table.insert(result,'top: '..tostring(row.gridy+w-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') else -- center or centre local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*0.6 table.insert(result,'top: '..tostring(row.gridy+(h/2)-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') end if shape.textSP and shape.textSP ~='0px' then table.insert(result,"letter-spacing: "..shape.textSP..';') end if shape.textLH and shape.textLH ~='120%' then table.insert(result,"line-height: "..shape.textLH..';') end table.insert(result,"vertical-align: middle;\">"..row.labelText.."</div>") end local function makeTextItem(result, row, shape, align, tlink, textItem, dotItem) local w,h,r=getsize(shape.shapeSize) table.insert(result,"<div ") if row.title ~= "" then table.insert(result," title=\""..row.title.."\" ") end local ty,bry,linkoffset = 0,0,0 local compy=0 local lh=tonumber(string.match(shape.textLH or '120',"%d+"))/100 if dotItem==1 or (dotItem==2 and row.posType~='n-line') then -- if there is a dotTag in the middle of the shape then use the tag settings ty=tonumber(string.match(shape.tagSize,"%d+")) or 0 table.insert(result,"style=\"position:absolute; line-height: 120%; top: "..tostring(row.gridy-ty*0.62)) table.insert(result,"px; left: "..tostring(row.gridx).."px; width: fit-content; ") table.insert(result,"text-align: center; color: "..getColor(shape.tagColor).."; background-color: transparent;") local trf="" if shape.tagAngle ~="0deg" then trf=" rotate("..shape.tagAngle..")" end table.insert(result, "transform: translateX(-50%)"..trf.."; font-size: "..shape.tagSize..";") if shape.tagSpacer~='0px' then table.insert(result, "letter-spacing:"..shape.tagSpacer..";") end else -- or add tfx settings for left, right or center align, colors, backgrounds, border-outline table.insert(result,'style="position:absolute; ') if dotItem==2 then -- dotTag is out at x,y so 85% ty=tonumber(string.match(shape.tagSize or '0',"%d+")) table.insert(result,'font-size: '..shape.tagSize..'; padding:0px 2px;line-height: 85%; top: '..tostring(row.dy+row.gridy-ty*0.52)) else -- it is labelText, so use textLH or 120% ty=tonumber(string.match(shape.textSZ or '11',"%d+")) if row.labelPos=='northwest' or row.labelPos=='northeast' then compy=-ty elseif row.labelPos=='southeast' or row.labelPos=='southwest' then compy=ty/2 end if row.labelPos and not (row.labelPos== 'auto' or row.labelPos=='') then bry=(select(2, string.gsub(row.labelText,"<br>", ""))*lh) -- is it a multiline text? expand by line-height /120%? if row.posType == 'with-line' then bry=0 end if row.labelPos=='bottom' or row.labelPos == 'south' then bry = 0 -- and shift by none, all or half elseif row.labelPos=='top' or row.labelPos == 'north' then if shape.shape=='image:' then bry= 1 + math.min(w/2-10,0)-bry*ty else bry= -bry*ty+2 end else bry=-bry*(ty/2 * lh) end end if row.posType == 'photo-panel' then bry=bry+3 end table.insert(result,'font-size: '..shape.textSZ..'; padding:0px 3px;line-height: '..shape.textLH..'; ') table.insert(result,'top: '..tostring(row.dy+row.gridy+compy+bry-ty*lh/2)) end table.insert(result,"px; left: "..tostring(row.dx+row.gridx).."px; color: "..getColor(shape.textCL).."; ") table.insert(result,"width: max-content; ") local trf="" if shape.textNG ~="0deg" then trf="rotate("..shape.textNG..")" end if shape.textOL~="0px" then table.insert(result,"background-color: "..getColor(shape.textBG).."; ") table.insert(result,"border: "..shape.textOL.." solid "..getColor(shape.textCL).."; border-radius:6px;") else table.insert(result,"background-color: transparent;") end if row.labelPos=="right" or string.find(row.labelPos,'east') then table.insert(result,"text-align: left; ") linkoffset=w if shape.textNG ~="0px" then table.insert(result,"transform-origin: left; transform: rotate("..shape.textNG.."); ") end elseif row.labelPos=="left" or string.find(row.labelPos,'west') then if shape.textNG ~="0px" then table.insert(result,"transform-origin: right;") end linkoffset=-w table.insert(result,"text-align: right; transform: translateX(-100%) "..trf.."; ") else table.insert(result,"text-align: center; transform: translateX(-50%) "..trf.."; ") end table.insert(result,'font-weight: normal; line-height: '..shape.textLH..'; letter-spacing:'..shape.textSP..'; vertical-align: bottom;') end if string.find(shape.textAT or '','bold') then textItem='<b>'..textItem..'</b>' end if string.find(shape.textAT or '','italic') then textItem='<i>'..textItem..'</i>' end if shape.textOL=='0px' and shape.textBG~='transparent' and dotItem==0 then table.insert(result,'\"><span style=\"background-color: '..getColor(shape.textBG)..'; color:inherit;\">'..textItem..'</span></div>') else table.insert(result,"\">"..textItem.."</div>") end if tlink~='' and not string.match(row.param1 or "","nolink") then table.insert(result,makeLinkBox(row.gridx+row.dx+linkoffset, row.gridy+row.dy+bry, 16, row.dotTag..' '..row.title,tlink)) end end local function getshapetable(row,shape) -- Construct CSS divs for a dot from shape and map data local result={} local w,h,rndg=getsize(shape.shapeSize) local tshape,tlink=checkfortooltip(row.title,row.dx,row.dy,row.dotTag,row.dlink,string.match(row.param1 or "","nolink") ) local align=row.labelPos or '' local offsetx,offsety=0,0 local ty=tonumber(string.match((shape.tagSize or 9),"%d+" )) if row.labelText and row.labelText~='' then ty=tonumber(string.match((shape.textSZ or 11),"%d+" )) else align='center' end -- it is just for dotTag, so justify center -- identify align value and extend offsets local widthzone,heightzone = (w/2)+1,(h/2)+1 local theta,r = math.deg( math.atan2(row.dy, row.dx)) , math.sqrt(row.dx^2 + row.dy^2) if align=='auto' or align=='' then if (theta < -112 or theta > 112) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "left" offsetx=1 elseif (theta > -68 and theta < 68) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "right" offsetx=-1 elseif theta <0 then offsety=ty/2-1 -- bottom else offsety=0-ty/2+1 -- top end elseif align=='left' or string.find(align,'west') then row.dx=row.dx - w/2 widthzone=4 offsetx=-1 elseif align=='right' or string.find(align,'east') then row.dx=row.dx + w/2 widthzone=4 offsetx=1 elseif align=='top' or align=='north' then if string.find(shape.shape,'curve') then row.dy=row.dy-(h/2)-12 heightzone=4 offsety=ty/2-1 else row.dy=row.dy - h-2 heightzone=4 offsety=ty/2-1 end elseif align=='bottom' or align=='south' then if string.find(shape.shape,'curve') then row.dy=row.dy+(h/2) heightzone=4 offsety=ty/2-1 else row.dy=row.dy + h+2 heightzone=4 offsety=0-ty/2-1 end end if shape.textEW ~= "0px" and not string.match(row.param1 or "","noline") then -- debugmsg('r-(w/2) > widthzone r-w/2='..tostring(r-(w/2))..', widthzone='..widthzone..', label='..(row.labelText or '')..', postype='..(row.posType or '')) makeLineTo(result, row.gridx+1, row.gridy-1, row.gridx+row.dx+offsetx, row.gridy+row.dy+offsety, shape.textEW, shape.textES,shape.textEC,1) -- debugmsg('n-line for '..row.code..':'..tostring(row.gridx+1)..', '..tostring(row.gridy-1)..', '..tostring(row.gridx+row.dx+offsetx)..', '..tostring(row.gridy+row.dy+offsety)..', with width '..tostring(shape.textEW)..'px '..(shape.textES or 'no style')..' and color '..shape.textEC) end if row.posType and row.posType=='mark-line' then if row.gridx2 and row.gridy2 then makeLineTo(result, row.gridx, row.gridy, row.gridx2, row.gridy2, tostring(row.mlWidth)..'px', row.mlStyle, getColor(shape.textEC),row.mlGap,shape) -- debugmsg('mark-line line drawn from '..row.code..' with width '..tostring(row.mlWidth)..'px '..(row.mlStyle or 'no style')..' and color '..getColor(shape.textEC)) end end if w ~= 0 then if shape.shape=='itriangle' then shape.shape='triangle' shape.shapeSize=tostring(w)..'px'..tostring(w/2)..'px' end if shape.shape=="triangle" then if shape.outlineWidth ~= "0px" then makeTriangle(result,row,shape,true,false) -- larger triangle to give the outline, if required end makeTriangle(result,row,shape,false,tshape) -- smaller triangle to fit over the top elseif shape.shape=="square" or shape.shape=="box" or shape.shape=='panel' then makeSquare(result,row,shape,tshape) elseif shape.shape=="circle" or shape.shape=="ellipse" then makeCircle(result,row,shape,tshape) elseif string.find(shape.shape,'image:')==1 then makeImage(result,row,shape) elseif shape.shape=="rulea" or shape.shape=='rule' then makeRuleA(result,row,shape) elseif shape.shape=="curvea" or shape.shape=="curvec" then makeCurveA(result,row,shape) else -- use a pathshape clipPath if shape.outlineWidth ~= "0px" then makeClipPath(result,row,shape,true,false) -- larger path-shape to give the outline, if required end makeClipPath(result,row,shape,false,tshape) end end if row.ppwidth and row.ppwidth>0 then makePhotoPanel(result,row,shape) end if shape.shape=='panel' and row.labelText then makePanelText(result, row, shape) else if row.dotTag and row.dotTag ~= "" then -- there is a dotTag if (row.dx==0 and row.dy==0) and w>0 then -- it is on the dot so if dotsize is not 0 any label is ignored makeTextItem(result, row, shape, align, tlink, '<b>'..row.dotTag..'</b>', 1) else if row.labelText and row.labelText~='' then -- tag and label both used makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.labelText, 0) else makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.dotTag, 2) -- tag is ouside the dot end end else if (row.labelText and row.labelText~='') then -- just the label. No tag makeTextItem(result, row, shape, align, tlink, row.labelText, 0) end end end if tlink and tlink~='' then table.insert(result,makeLinkBox(row.gridx, row.gridy,w+3,row.dotTag..' '..row.title,tlink)) end return table.concat(result) end local function getmapframecontent(args,use) local result, comma= {}, '' local propertyTable= {} local color=splitItem(string.lower( args['map-data-color'] or 'dark orange'), 2) if use == 'basemap' then table.insert(result, '[') propertyTable.stroke = "#000000" propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') if color[2] then propertyTable['stroke-opacity'] = tonumber(color[2] or '20')/100 end else propertyTable.title = args['map-data-text'] or args['map-data'] or '' propertyTable.stroke = getColor(color[1]) propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') propertyTable['stroke-opacity'] = tonumber(color[2] or '100')/100 end if args['map-data-inverse'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geomask', ids = args['map-data-inverse'], properties = {title = (args['map-data-text'] or ''), stroke = '#555555', fill = '#555555', ['fill-opacity'] = 0.1, ['stroke-width'] = 1, ['stroke-opacity'] = 0.5 } } table.insert(result, mapJson) comma=', ' end if args['map-data-heavy'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-heavy'], properties = { title = args['map-data-heavy'], stroke = propertyTable.stroke, ['stroke-width'] = 9, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.2 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data-light'] and args['map-data-light'] ~= '' then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-light'], properties = { title = args['map-data-light'], stroke = propertyTable.stroke, ['stroke-width'] = 3, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.4 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data'] then propertyTable['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.2 local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data'], properties = propertyTable } table.insert(result, comma .. mapJson) comma=', ' end if args['map-wdqs'] then -- inserts a SPARQL wdqs query to find geopoints, geoshapes etc from wikidata local mapSparql = mw.text.jsonEncode{ type = 'ExternalData', service = args['map-wdqs-type'] or 'geopoint', query = args['map-wdqs'], } table.insert(result, comma .. mapSparql) -- debugmsg('mapSparql = ' .. mapSparql) comma=', ' end if args['map-raw'] then table.insert(result, comma .. args['map-raw']) end if use=='basemap' then table.insert(result,']') end return table.concat(result) end --eg | minilocator=filename,bottom right,132px153px, 38%,60%, 22px local function makeLocatorMap (args, result) local miniFile,pos,itemlist,miniW,miniH, miniX,miniY,miniBox, miniBH if args['mini-locator'] then pos,miniFile=extractItem(args['mini-locator']) -- first item is filename, in quotes if it includes commas itemlist=splitItem(pos,5) -- put items in a table filename removed, position,WpxHpx, x%y%,box pos=itemlist[2] or 'right' miniW,miniH=getsize(itemlist[3]) miniX=tonumber(string.match(itemlist[4] or '0','[%d]+'))*miniW/100 miniY=tonumber(string.match(itemlist[5] or '0','[%d]+'))*miniH/100 miniBox=tonumber(string.match(itemlist[6] or '0','[%d]+')) miniBox=miniBox*miniW/100 miniBH=miniBox * maplist.height/maplist.width elseif args['mini-file'] then miniFile = args['mini-file'] pos=string.lower(args.minimap or 'right') if pos=='file' then pos='right' end miniW,miniH = tonumber(args['mini-width'] or 60), tonumber(args['mini-height'] or 60) -- find top left corner of locator miniBox,miniBH=tonumber(args['minimap-boxwidth'] or '0'),0 -- firm up pos offsets for dot (with % or not) and boxsize if any, miniX, miniY=args['minipog-gx'],args['minipog-gy'] if not miniX then miniX=tonumber(args['minipog-x'] or '0') else miniX=tonumber(miniX)*tonumber(miniW)/100 end if not miniY then miniY=tonumber(args['minipog-y'] or '0') else miniY=tonumber(miniY)*tonumber(miniH)/100 end if args['minipog-gx'] then miniBox=miniBox*miniW/100 end miniBH=miniBox * maplist.height/maplist.width else return end local miniTop,miniLeft=0,1 if not string.find(pos,'top') then --only use top left, as link box is in top right or put bottom left or right miniTop=maplist.height+2-miniH if string.find(pos,'right') then miniTop=miniTop-15 -- to avoid (c) line miniLeft=maplist.width-1-miniW end end table.insert(result,'<div style="position: absolute; outline-width: 1px; outline-style: solid; outline-color: white;') table.insert(result,'top: '..tostring(miniTop)..'px; left:'..tostring(miniLeft)..'px; width:'..tostring(miniW)..'px;' ) table.insert(result,'background-color:transparent; color:inherit;">[[File:'..miniFile..'|'..tostring(miniW)..'px|File:'..miniFile..']]</div>') if miniX and miniX>0 then if args['minipog-y'] then miniY=miniY/1.04 end if args['minipog-x'] then miniX=miniX/1.04 end if miniBox<1 then table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-3-6)..'px; left:'..tostring(miniX+miniLeft-3)..'px;') table.insert(result, 'width: 6px; background-color:transparent; color:inherit;">[[File:Red pog.svg|6px|link=]]</div>') else table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-miniBH/2)..'px; left:'..tostring(miniX+miniLeft-miniBox/2)..'px;') table.insert(result, 'width: '..tostring(miniBox)..'px; height:'..tostring(miniBH)..'px; outline:1px solid #AA1205; background-color:#AA120522; color:inherit;"></div>') end end end local function makeArcText(args,result,nval) local items, itemlist='',{} local arcText='' if args['arc'..nval] then items=convertCoords (args['arc'..nval]) items,arcText=extractItem(items) -- first item is text, in quotes if it includes commas itemlist=splitItem(items,9) -- put items in a table: text, lat,lon,size,color,angle,gap,radius,ellipse if itemlist[4]=='' then itemlist[4]='12' end itemlist[4]=string.match((itemlist[4] or '12'),"[%.%-?%d]+") if itemlist[5]=='' then itemlist[5]='grey' end itemlist[6]=string.match((itemlist[6] or '0'),"[%.%-?%d]+") itemlist[7]=string.match((itemlist[7] or '0'),"[%.%-?%d]+") itemlist[8]=string.match((itemlist[8] or '0'),"[%.%-?%d]+") end if args['arc-coord'..nval] then local itemTab=splitItem(convertCoordsTrad (args['arc-coord'..nval]),2) maplist.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) maplist.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) else maplist.lat=tonumber(string.match(args['arc-lat'..nval] or itemlist[2] or '0',"[%.%-?%d]+")) maplist.lon=tonumber(string.match(args['arc-lon'..nval] or itemlist[3] or '0',"[%.%-?%d]+")) end local arcX,arcY=maptogrid(maplist,6) arcText = args['arc-text'..nval] or arcText local fontSize =tonumber(args['arc-text-size'..nval] or itemlist[4] or '12') local textColor=getColor(string.gsub(args['arc-text-color'..nval] or itemlist[5] or 'grey','[%s]+','') ) local arcAngle= tonumber((args['arc-angle'..nval]) or (itemlist[6]) or '45')-90 local arcRadius =tonumber(args['arc-radius'..nval] or itemlist[8] or '0.05') local arcGap = tonumber(args['arc-gap'..nval] or itemlist[7] or '1')* ( ( math.sin(8-math.rad(arcRadius))^8 )+0.4 )*( ( fontSize+6 )/15 ) arcRadius=450*arcRadius*0.75 local ellipseFactor=tonumber(args['ellipse-factor'..nval] or itemlist[9] or '1') local arcRotate =arcAngle+90 if arcGap<0 then arcRotate=arcAngle-90 end local latF=arcY - fontSize + (0-(math.sin(math.rad(arcAngle))) * arcRadius) local lonF=arcX - fontSize +(0-(math.cos(math.rad(arcAngle))) * arcRadius) local step=1 for codepoint in mw.ustring.gcodepoint( arcText ) do -- block step=1,#arcText do table.insert(result,'<div style="position: absolute;') local posY=tostring(round( (latF + (math.sin(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius)) *ellipseFactor,2))..'px;' local posX=tostring(round( (lonF + (math.cos(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius))/ellipseFactor,2))..'px;' table.insert(result,' top: '..posY..' left: '..posX..' transform: rotate( '..tostring(round(arcRotate +((step-1)*arcGap)),2)..'deg);') table.insert(result,'width:'..tostring(fontSize*2)..'px; text-align: center; background-color:transparent; color: '..textColor..';') table.insert(result,'vertical-align: baseline; font-size: '..tostring(fontSize)..'px;">'..mw.ustring.char(codepoint)..'</div>') step=step+1 end end local function makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) local item={} itemdescription=stripdivs(itemdescription or '') local templon=lon if lon > 180 then templon=lon-360 end --for hemisphere+ or -1 dots if lon < -180 then templon=lon+360 end -- use 'real' coordinates for geohack label, while retaining shifted coords for plot if itemcolor=='transparent' then itemcolor='white' end itemcolor=getColor(itemcolor) -- ensure no opacity, which breaks maplink if string.find(itemcolor,'#')==1 and #itemcolor>7 then itemcolor=string.sub(itemcolor,1,7) end table.insert(item, '{ "type": "Feature", "properties": {') table.insert(item, ' "title": "'..itemtitle..'",') table.insert(item, ' "description": "'..itemdescription) table.insert(item, ' ([https://geohack.toolforge.org/geohack.php?params='..tostring(lat)..';'..tostring(templon)) table.insert(item, '_dim:2000 '..tostring(lat)..','..tostring(templon)..'])",') table.insert(item, ' "marker-symbol": "-number-'..string.gsub(group,'%W','')..'", "marker-size": "medium", "marker-color": "'..itemcolor..'" },') table.insert(item, ' "geometry": {"type": "Point", "coordinates": ['..tostring(lon)..','..tostring(lat)..'] } }') return table.concat(item) end local function makeLegendBox(result,args) local legend ={} local line,count, maxWidth='',1,8 local item line,item=extractItem(args.legendBox or '') local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end legend.Text = a -- convert hats to line breaks line=splitItem(line,6) -- (text, size, poition,background color, text/outline color, param options) legend.Size=line[2] or '150px80px1px' legend.Pos=line[3] or '10px10px' legend.Background=line[4] or 'beigeground' legend.Color=line[5] or 'darkbrown' legend.Param= line[6] or '' local argnum,legendCount,titleHeight='1',1,0 if (legend.Text and legend.Text~='') then titleHeight=15+(13.4*(select(2, string.gsub(legend.Text,"<br>", "")))) end local legendLine,legendGroup,legendY={},{},{} while args['legendItem'..argnum] do -- assign legendLine, legendGroup, legendY for each dot line,legendLine[legendCount] = extractItem(args['legendItem'..argnum] or '') line=splitItem(line,3) legendGroup[legendCount]=line[2] or argnum if shapeList[legendGroup[legendCount]] then maxWidth=math.max(tonumber(string.match(shapeList[legendGroup[legendCount]].shapeSize or '10','[%d]+')),maxWidth) else maxWidth=math.max(tonumber(string.match(shapeList['1'].shapeSize or '10','[%d]+')),maxWidth) end maxWidth=maxWidth+1 if line[3] then legendY[legendCount]=tonumber(string.match(line[3],'[%d]+')) else legendY[legendCount] = 3+maxWidth*(legendCount-1)+titleHeight -- if (legend.Text and legend.Text~='') then legendY[legendCount]=legendY[legendCount]+15 end end legendCount=legendCount+1 argnum=tostring(legendCount) end local w,h,r=getsize(legend.Size or '') local x,y=getsize(legend.Pos or '') local div=mw.html.create ('div') div:css('position', 'absolute') div:css('outline', '1px solid'..getColor(legend.Color)) if r~=0 then div:css('border-radius',tostring(r).."px") end div :css('top', y.."px") :css('left', x.."px") :css('width', w.."px") :css('height', h.."px") :css('line-height','105%') :css('background-color', getColor(legend.Background) ) :css('color','inherit') if not string.find(legend.Param,'noshadow') then div:css('box-shadow', '2px 2px 4px #33203335') end div:tag( 'div' ) :css('position', 'absolute') :css('top','1px') :css('left', (w/2).."px") :css('width',(w-8)..'px') :css('text-align', 'center') :css('color', getColor(legend.Color)) :css('transform', 'translateX(-50%)') :css('font-size','11px') :wikitext(legend.Text) :done() for lct=1,legendCount-1 do --local t=legendGroup[lct] local shape=shapeList[legendGroup[lct]] or shapeList['1'] local row={} row.gridx=3+maxWidth/2 row.gridy=(legendY[lct] or 0) + 5 if shape.shape=='image:' then row.gridy=row.gridy+6 end row.dx=0 row.dy=0 local legendShape= getshapetable(row,shape) div:wikitext(legendShape) :tag( 'div' ) :css('position', 'absolute') :css('top',(legendY[lct] or 0)..'px') :css('left', (maxWidth+6)..'px') :css('width', (w-maxWidth-6)..'px') :css('text-align', 'left') :css('line-height','103%') :css('color', getColor(legend.Color)) :css('font-size','10px') :wikitext(legendLine[lct]) :done() end div:allDone() table.insert(result,tostring(div)) end local function getQset (qValue,pValue,argNumber,args) local result={} local sortType=string.lower(args['geo-list-sort'] or 'a') local testValue='0' if sortType == 'alpha' or sortType == 'a' or sortType == 'ah' or sortType == 'al' then testValue = mw.wikibase.getLabel(qValue) elseif sortType == 'value' or sortType == 'v' or sortType == 'vh' or sortType == 'vl'then if qValue==nil or pValue == nil then debugmsg(mw.addWarning((qValue or 'nil')..' Qnumber for '..(pValue or 'nil'))) else if mw.wikibase.getBestStatements(qValue,pValue)[1] then testValue = mw.wikibase.getBestStatements(qValue,pValue)[1].mainsnak.datavalue.value.amount or '0' if pValue == 'P2046' then testValue = convertToSqKm(testValue, mw.wikibase.getBestStatements(qValue,pValue)[1].mainsnak.datavalue.value.unit) end end end elseif sortType == 'north' or sortType == 'n' or sortType == 'nh' or sortType == 'nl' then if mw.wikibase.getBestStatements(qValue,'P625')[1] then testValue = mw.wikibase.getBestStatements(qValue,'P625')[1].mainsnak.datavalue.value.latitude or 0 end elseif sortType == 'west' or sortType == 'w' or sortType == 'wh' or sortType == 'wl' then if mw.wikibase.getBestStatements(qValue,'P625')[1] then testValue = mw.wikibase.getBestStatements(qValue,'P625')[1].mainsnak.datavalue.value.longitude or 0 end end result[1]=testValue result[2]=argNumber return result end function p._main ( args ) local result={} local frame=mw.getCurrentFrame() local dotTable={} local magVal,scaleVal = args.magnify,'' local origH,origW=maplist.height,maplist.width if magVal then --set up the values needed to magnify the top-right portion of the map magVal=tonumber(string.match(magVal or '0',"[%.%-?%d]+")) or 1 if magVal>1 and magVal <=2 then maplist.height= round(maplist.height/magVal,0) maplist.width=round(maplist.width/magVal,0) scaleVal='transform: scale('..magVal..') translateY('..tostring((origH-maplist.height)/2)..'px);' else magVal=1 end end -- set up the three nested div boxes (plus an extra if centered) to put the map plus title/caption area, in an appropriate frame on the page if args.float=='center' or args.float=='centre' then table.insert(result,'<div class="center"><div class="thumb tnone">') elseif args.float=='left' then table.insert(result,'<div class="thumb floatleft">') else table.insert(result,'<div class="thumb floatright">') end table.insert(result,'<div class="thumbinner" style="position: relative; top: 0px; right: 0px; width: '..(args.width or "400")..'px;">') if args.title then table.insert(result,'<div class="center" style="font-weight:bold">'..args.title..'</div>') end if magVal and magVal >1 and magVal<=2 then table.insert(result,'<div class="thumbinner noresize" style="display:block; position: relative; outline:0px; border:0px; padding:0px; background-color:transparent; color:inherit;') table.insert(result,'top: 50%; right: '..tostring(0-((origW-maplist.width))/2)..'px; ') table.insert(result,'height: '..origH..'px; width: '..maplist.width..'px; '..scaleVal..'">') else magVal=1 table.insert(result,'<div class="thumbinner noresize" style="position: relative; outline:0px; border:0px; padding:0px;') table.insert(result,'top: 0px; right: 0px; ') table.insert(result,'height: '..maplist.height..'px; width: '..maplist.width..'px ">') end --add any boundary Qvalues to map-data-light if requested if args['geo-region'] and (args['geo-boundaries'] == '1') then local qmaps=args['map-data-light'] or '' if qmaps ~='' and string.sub(qmaps,#qmaps) ~= ',' then qmaps=qmaps..',' end local geoRegion = args['geo-region'] -- or add base-page here local geoSubRegion = args['geo-sub-region'] or 'P150' if not mw.wikibase.isValidEntityId(geoSubRegion) then geoSubRegion = 'P150' end if mw.wikibase.isValidEntityId(geoRegion) then local ct=1 local qtemp while (mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct]) do -- debugmsg('table '..ct..' - '..mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id) qtemp = mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id or nil if qtemp then if mw.wikibase.entityExists(qtemp) then qmaps=qmaps..qtemp..',' -- see if there is a geocode boundary Qvalue to add else debugmsg(mw.addWarning('unable to add geocode value for '..mw.wikibase.getLabel..','..qtemp)) end end ct=ct+1 end end if qmaps and qmaps ~= '' then -- add back the created/expanded list of boundary Qvalues, and remove trailing comma args['map-data-light'] = string.sub(qmaps,1,#qmaps-1) end end -- Create the basemap using mapframe local mapframecontent=getmapframecontent(args,'basemap') table.insert(result, frame:extensionTag{ name ='mapframe', content=mapframecontent, args={width=tostring(maplist.width), height=tostring(maplist.height), zoom=tostring(maplist.zoom), longitude=tostring(maplist.lonbase), latitude=tostring(maplist.latbase), mapstyle=maplist.mapstyle, frameless=true } } ) --Add coverall box to block the unhelpful links from mapframe - which wouldn't include all the dots. Reinstate some links to osm and wikimedia table.insert(result,'<div style="position: absolute;width:'..tostring(maplist.width)..'px; height:'..tostring(maplist.height)..'px;') table.insert(result,'top:0px;left:0px;background-color:#FFFFFF00; color:inherit;"></div>') --Add replacent hover-links for OpenStreetMap and maps terms and conditions table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-13)..'px; width: 12px; height: 12px">') table.insert(result,'[[file:Transparent.svg|12px|link=https://www.openstreetmap.org/copyright|'..aboutOSM..']]</div>') table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-110)..'px;') table.insert(result,'width: 12px; height: 12px background-color: transparent; color:inherit;">') table.insert(result,'[[file:Transparent.svg|12px| link=https://foundation.wikimedia.org/wiki/Policy:Maps_Terms_of_Use|'..termsOfUse..']]</div>') -- Add scale-line if not args.scalemark or args.scalemark~='0' then local top=maplist.height-42 local left=maplist.width-61-(tonumber(args.scalemark or '1')) local minipos=string.lower(args.minimap or '') -- scalemark gets pushed left if it would be behind the minimap if minipos~='' and not(string.find(minipos,'left') or string.find(minipos,'top') ) then local offset=tonumber(args.scalemark or '1')-tonumber(args['mini-width'] or '60') if offset<1 then left=maplist.width-61-tonumber(args['mini-width'] or '60') end end if maplist.width-left >216 then top=top+14 end -- shunt scaleline down if it is beyond the copyright stuff local scalek,scalem=getScale(maplist.zoom, maplist.latbase, magVal) local magReduce='' table.insert(result,"<div style=\"display:inline-block; position: absolute; background-color: #111111") table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;M0,8 l0,4 l20,0 l0,-4 l-0.3,0 l0,3.7 l-19.4,0 l0,-3.7 z&#39;) ") table.insert(result,"; width:20px") -- path is a 20px20px box, and then rescales if magVal==1 then table.insert(result,"; top:"..tostring(top-1).."px") table.insert(result,"; left:"..tostring(left+16).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5)..", "..tostring(1.5)..")") else -- shrink the scalemark to compensate for magnification table.insert(result,"; top:"..tostring(top-(magVal*0.28)).."px") table.insert(result,"; left:"..tostring(left+(16*(magVal*1.15))).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5*(1/magVal))..", "..tostring(1.5*(1/magVal))..")") magReduce= 'scale('..tostring(1/magVal)..')' end table.insert(result,"\"></div>") table.insert(result,'<div style="position: absolute; top: '..tostring(top)..'px; left: '..tostring(left+47)..'px; font-size: 9.5px; line-height: 126%; width: fit-content;') table.insert(result,'color: #444433; background-color: transparent; text-align: right; transform: '..magReduce..' translateX(-100%);">'..scalek..'<br>'..scalem..'</div>') end --Set up the shapeList and dotList tables, to provide data to go on the map local sgNumbers,sgSortable={},{} --s1,s2 sgNumbers["1"]="1" if args.useFormatStyle and args.useFormatStyle=='shortstyle' then shapeList=ParseShapeTypes (shapeList,args,"1") for argindex,argv in pairs(args) do -- build a list of all the numbered sg's that have been used if string.find(argindex,"sg[a-f,n%d]+") == 1 then -- only look through the sga,sgb, sgc,sgd,sge,sgf and sgn args local x=string.match(argindex,"[%d]+") -- find its number if x and not sgNumbers[x] then sgNumbers[x]=x end -- only add if not already found end if string.find(argindex,"sg[a-f]H") == 1 and highlightNum then highlightOption=true end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form for k,v in pairs(sgSortable) do -- work through the sorted list, parsing each set of sg's in turn, from 1 upwards shapeList=ParseShapeTypes (shapeList,args,v) end if highlightOption==true then shapeList=ParseShapeTypes (shapeList,args,'H') end end local dotList,dotresult,dotItemTable,dotGroupList={},{},{},{} if (not args.useFormatStyle) or args.useFormatStyle=='standardstyle' then local dotmax=0 local qset={} for indx,val in pairs(args) do if string.match(indx,'mark%-coord[%d]+') or string.match(indx,'lat[%d]+') or string.match(indx,'geo%-data[%d]+') then dotmax=math.max(dotmax, tonumber(string.match(indx,"[%d]+"))) if string.match(indx,'geo%-data[%d]+') then local argnum=string.sub(indx,9,#indx) or '' if args['mark-title'..argnum] == 'none' or (string.find(args['mark-title'..argnum] or 'x','nolist')== 1) then -- debugmsg('sort values ignored '..argnum..', '..args['mark-title'..argnum]) else qset[val]=getQset (val, (args['geo-data-type'] or 'P1082'), argnum, args) end end end if (indx=='shapeH') or (indx=='shape-colorH') or (indx=='shape-outlineH') or (indx=='label-colorH') then highlightOption=true end end local qlist=args['geo-data-list'] if qlist then if string.sub(qlist,#qlist) ~= ',' then qlist=qlist..',' end else qlist='' end local geoRegion = args['geo-region'] -- or add base-page here local geoSubRegion = args['geo-sub-region'] or 'P150' if not mw.wikibase.isValidEntityId(geoSubRegion) then geoSubRegion = 'P150' end if (args['find-geo-dots'] or '1') ~= '0' and geoRegion and mw.wikibase.isValidEntityId(geoRegion) then local ct=1 local qtemp while (mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct]) do -- debugmsg('table '..ct..' - '..mw.wikibase.getBestStatements(adminRegion,adminSubRegion)[ct].mainsnak.datavalue.value.id) qtemp = mw.wikibase.getBestStatements(geoRegion,geoSubRegion)[ct].mainsnak.datavalue.value.id or nil if qtemp then qlist=qlist..qtemp..',' -- add subregion Qvalue to the list end ct=ct+1 end end if qlist and #qlist>1 then -- create a table from the csv list local _, c = qlist:gsub(",","") local qtab=splitItem(qlist,c) for xd,yd in pairs(qtab) do yd=string.upper(yd) if qset[yd] == nil and mw.wikibase.isValidEntityId(yd) then dotmax=dotmax+1 qset[yd]=getQset (yd, (args['geo-data-type'] or 'P1082'), tostring(dotmax), args) args['geo-data'..tostring(dotmax)] = yd -- debugmsg(dotmax..' = '..(string.upper(yd) or 'Q Not found')..': '..(mw.wikibase.getLabel(yd) or 'label Not found')..': '..(mw.wikibase.getSitelink(yd) or 'link Not found')) elseif not mw.wikibase.isValidEntityId(yd) then debugmsg(mw.addWarning(yd..' is not a [[wikidata]] page. geo-data-list needs comma-separated Qvalues' )) else debugmsg(mw.addWarning(yd..' is duplicate')) end end end -- qset contains set of Qvalues and can be from wikidata geo-region, list of geo-data-list Q values or individual geo-data1= etc local ts = {} -- table to hold the keys local useQset=false for ky, val in pairs(qset) do table.insert(ts, {key = ky, value = val[1], num=val[2]}) useQset=true end if useQset then local order = string.lower(args['geo-list-sort'] or 'a') -- sort the list from previously stored sortkey, either Lowest or Highest first if string.find(',al,vl,nl,wl,west,w,alpha,a,' , ','..order..',') then table.sort(ts, function (a,b) return lessthan(a.value, b.value) end) else table.sort(ts, function (a,b) return morethan(a.value, b.value) end) end for ky,val in ipairs(ts) do -- assign values from sorted list to numbered parameter args['numbered'..val.num] = ky -- The numbered parameter becomes the value for each dot that has a geo Qvalue, from manual, list or wiki sources. end end dotItemTable=tradstyleParseShapes(args,dotItemTable,dotmax) for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) or args['geo-data'..x] then sgNumbers[x]=x -- add it to the list end end else for indx in pairs(args) do if string.match(indx,'dot[%d]+') then table.insert(dotList,indx) end --add the index name for dot1, dot2 etc to dotList end table.sort(dotList,morethan) for indx,dotName in pairs(dotList) do dotresult=ParseData(args,string.match(dotName,'[%d]+') ) -- using each dot number, assign the settings for each dot to a dotresult item line table.insert(dotItemTable,dotresult) -- and store that item line within the dotItemTable end end for arcVal = 65,91 do -- check through args looking for any arcs local arcLetter=string.char(arcVal) if args['arc-text'..arcLetter] or args['arc'..arcLetter] then makeArcText(args,result,arcLetter) end end local dotdivs='' local ddots=0 if dotItemTable[1] then local ddots=(dotItemTable[1].lat or 0)+(dotItemTable[1].lon or 0) end local fgroup='F'..tostring(maplist.latbase+maplist.lonbase+ddots ) local FullscreenList={} local addcomma='' for i,dotitem in pairs(dotItemTable) do -- working throug each dot item, merge the dot and shape values into a full set of css text local dotgroup= dotitem.group or "0" if dotitem.posType=='mark-line' and dotitem.markDest then --find destination xy values for any mark-lines for n,v in pairs(dotItemTable) do if v.code == dotitem.markDest then dotitem.gridx2=v.gridx dotitem.gridy2=v.gridy break end end end local qtype=dotitem.group -- find which shape group each dot has been assigned --debugmsg('dotgroup='..qtype..', sg='..(sgNumbers[qtype] or 'nil')..' , shapeList='..shapeList[qtype].shape) if not sgNumbers[qtype] then qtype="0" end --shapeList[dotitem.group] will give access to the shape values for that dot if highlightNum==dotitem.code and highlightOption==true and shapeList['H'] then table.insert(result, getshapetable(dotitem,shapeList['H'])) else table.insert(result, getshapetable(dotitem,shapeList[qtype])) -- Add the actual css instructions for each dot end if shapeList[dotgroup] and not string.find((dotitem.param1 or ''),'nomap') then -- only add if not excluded with 'nomap' labelText local ftext='' if dotitem.dotTag~='' and not string.match(dotitem.labelText or '','[%d]') then ftext=stripdivs(dotitem.dotTag or '')..' <br>' end if (dotitem.labelText ~= ftext) and dotitem.dotLink =='' then ftext=ftext..' '..stripdivs(dotitem.labelText)..'<br>' end if (dotitem.dotLink) and (dotitem.dotLink ~='') then ftext=ftext..dotitem.dotLink..'<br>' end if dotitem.imageName then ftext=ftext..'[[File:'..dotitem.imageName..'|250px]]' end table.insert(FullscreenList,1, makeFullscreenItem (string.gsub(ftext,"[\n]+"," "), dotitem.info,round(dotitem.lat,5),round(dotitem.lon,5),fgroup,shapeList[dotgroup].shapeColor)..addcomma ) addcomma=', ' end -- makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) -- Always add to start of list, to reverse the sequence, and separate with commas except for first item, which is now at the end end if args.legendBox then makeLegendBox(result,args) end if args.minimap or args['mini-locator'] then makeLocatorMap(args,result) end -- add tag link and details for fullscreen version addcomma='' if (mapframecontent or '[]') ~= '[]' then addcomma=',' end mapframecontent=getmapframecontent(args,'fullscreen') local contentstart='[ '..mapframecontent..addcomma..'{ "type": "FeatureCollection", "features": [ ' --extra features after first square bracket local contentend=' ] } ]' table.insert(result, '<div style="position: absolute;top: 9px;left: '..tostring(maplist.width-34)..'px">') table.insert(result, '<div style="color: white; opacity:100; font-size: 19px; font-weight:normal; text-align: left;">') table.insert(result, frame:extensionTag{ name ='maplink', content=contentstart..table.concat(FullscreenList)..contentend, args={zoom=tostring(maplist.zoom+1), class='no-icon', frameless='1', latitude=tostring(maplist.latbase), longitude=tostring(maplist.lonbase), --add invisble 'en-spaces' for tooltip text='<div title="'..fullscreenlinktext..'">&nbsp;&nbsp;&nbsp;</div>'} } ) table.insert(result,'</div></div>') -- end of maplink ----- -- add closing div for main map table.insert(result,'</div>') -- collate caption material to go in the outer div class if captionTable ~= '' and (not args['auto-caption']) then args['auto-caption']='on' end --default to include list if using geo values local autocaption=string.lower(args['auto-caption'] or 'no') local autoOff=autocaption:match("(%w+)(.*)") -- select the first word in autocaption and see if it is a negativeAnswer) if negativeAnswer[autoOff] then captionTable='' end if args.caption or (not negativeAnswer[autoOff]) then table.insert(result,'<div class="thumbcaption" style="text-align:left">') if args.caption then table.insert(result,args.caption) if captionTable ~= '' then table.insert(result,'<hr>') end end end local columns=tonumber(autoOff:match("[%d]+") or '1') if columns>1 then columns=round(maplist.width/(columns*17), 0) end -- convert from em to px for historical reasons --for k in pairs(dotList) do capchk=capchk..(args["dotlink"..k] or '') end local capchk=nil local captionList = {} for key, value in pairs(dotItemTable) do -- only add an autocaption line if there is both a dotTag and a dotLink line available and it is not marked as nolist if value.dotTag and value.dotTag~='' and (not string.find(value.param1 or '','nolist')) and string.gsub(value.dotLink or '',"%s+","")~='' then table.insert(captionList, {key = key, value = value}) if captionTable == '' then capchk=true else -- add the next line of content to the html table capchk=false end end end if captionTable ~= '' then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) for k,v in pairs(captionList) do local nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag local ngrp=v.value.group if captionTableText[ngrp] then -- debugmsg('table text for'..v.value.dotTag..', '..captionTableText[v.value.group][1]..', '..captionTableText[v.value.group][2]..', '..captionTableText[v.value.group][3]..' ('..captionTableText[v.value.group][4]..')') local c1,c2 c1,c2=checkColors(shapeList[ngrp].shapeColor) local cDot='<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;' cDot=cDot..'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div>' captionTable = htmlTableLine(captionTable,cDot,captionTableText[ngrp][1], captionTableText[ngrp][2], captionTableText[ngrp][3], captionTableText[ngrp][4]) end end end if capchk and (not negativeAnswer[autoOff]) then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) -- local myDivision = string.gsub((args.toggletext or toggletext), "%s+", "") if string.find(autocaption,'collaps') then table.insert(result,'<div class = "wikitable mw-collapsible ') if string.find(autocaption,'collapsed') then table.insert(result,'mw-collapsed') end table.insert(result,'" style="width:100%; line-height: 17px; border-style:none; text-align: center"><b>'..(args.toggletext or toggletext)..'</b>') end if string.find(autocaption, 'columns=') then columns=string.match(autocaption,'[%d]+',string.find(autocaption, 'columns=') ) end table.insert(result,'<div class="mw-collapsible-content" style="column-count:'..columns..'; column-rule:solid 1px;text-align:left;padding-top:5px">') table.sort(dotList,lessthan) local nval,ngrp='','0' for k,v in pairs(captionList) do nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag ngrp=v.value.group or '0' if v.value.dotLink and v.value.dotLink~='' and nval and nval~='' then local c1,c2 if nval==args.highlight then c1,c2=checkColors(shapeList['H'].shapeColor) else c1,c2=checkColors(shapeList[ngrp].shapeColor) end table.insert(result,'<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;') table.insert(result,'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div> '..v.value.dotLink..'<br>') end end table.insert(result,'</div>') -- end for caption-content div if string.find(autocaption,'collaps') then table.insert(result,'</div>') end -- end for toggle frame end if captionTable ~= '' then table.insert(result,captionTable..'</table>') end if args.caption or (not negativeAnswer[autoOff]) then table.insert(result,'</div>') end -- end for whole caption frame table.insert(result,'</div></div>') -- outer two frames if args.float == 'center' or args.float=='centre' then table.insert(result,'</div>') end if args['show-new-format'] == 'hints' then -- provide a 'format hint panel' in the 'Preview Box' local w="<small>Below are some template hints for the 'sga' compressed version, " w=w..'{{tl|OSM Location dots}}. It can use these, instead of the more verbose {{tl|OSM Location map}} parameter format. ' w=w..'Data is divided between a "ShapeGroup" and the "Dots", so that a single shapeGroup can be used for multiple dots on the map. ' previewMsg(w..'(nb. the "group" value can be the number or an assigned name of a shapeGroup)</small>') w='<small>{{tl|OSM Location dots}}: | dot(n)=group,lat,lon,dotTag | ' w=w..'dotlink=link/tooltip | dotlabel=label,position,dx,dy,param1,info | dotpic=filename <br>' w=w.."(nb. param1 options include 'nolink' 'nolist' 'nomap' 'hemisphere-1' 'hemisphere+1', 'noline' - quotes not required, separate with spaces).<br>" w=w..'| sga = Shape,Sizepx,Color,Angledeg | sgb= OutlineWidth,Color,Style | sgc=TextSize,Color,Angle,bold italic <br>' w=w..'| sgd=TagSizepx,Color,Spacer,Angledeg | sge=LineWidth,Color,Style | sgf=TextSpacingpx,LineHeight%,Outlinepx,backgroundColor<br>' w=w..'| sgn=Name (optional, to assign a meaningful name) | sgp=Parent (can be the name or number of the parent shapeGroup. ' previewMsg(w..'Each shapegroup will inherit values from a parent, stretching back to "sga1" and its default values.)</small>') if #pmsg then local dbg={} for i,x in pairs(pmsg) do table.insert(dbg,x..'<br>') end dbg=mw.addWarning(table.concat(dbg)) table.insert(result, dbg) end end if args.coordtest then debugmsg(mw.text.nowiki(args.coordtest)) end for i,x in pairs(msg) do table.insert(result, x..'<br>') end return table.concat(result) end function p.main(frame) local args = getArgs(frame) local itemTab={} maplist.width=tonumber(args.width) or 400 maplist.height=tonumber(args.height) or 300 if args.coord then itemTab=splitItem(convertCoordsTrad (args.coord),2) maplist.latbase=itemTab[1] maplist.lonbase=itemTab[2] else maplist.lonbase=tonumber(args.lon) or 5 maplist.latbase=tonumber(args.lat) or 0 end maplist.zoom=tonumber(args.zoom) or 1 visibleLinks=args.showlinks highlightNum=args.highlight if args.nolabels=='1' then maplist.mapstyle='osm' else maplist.mapstyle='osm-intl' end if maplist.width >= 400 and not(args['show-q-values']) then args['show-q-values'] = '1' -- default to on if wider than 400px elseif maplist.width < 320 then args['show-q-values'] = '0' end -- turn off Qvalus if frame is too narrow, even if requested return p._main(args) end return p gqsmiqj591a951mg947o6gd8jfmzvze 204460 204459 2026-07-05T15:02:47Z HaxelKaszëba 17492 204460 Scribunto text/plain require('strict') local delink=require('Module:Delink').delink local getArgs = require('Module:Arguments').getArgs local p = {} local maplist={} local sgNames={} local highlightOption=false local highlightNum local visibleLinks -- This module creates framed maps of anywhere in the world, at the required scale, and enables annotations, -- dots, shapes, lines and other ways to customise the area of the map being shown. It also provides a link -- to an interactive fullscreen version, which has locator dots instead of annotations and shapes. -- This is the 2025 successor module to a wiki-markup template version of 2024, which itself was a successor -- to the 'Graph'/VEGA driven template that was begun in 2016, until the Vega version was switched off in 2023. -- This module is called from template {{OSM Location map}}, which uses the same parameter formats as before. -- In addition it will be possible to use a more concise parameter format using the template {{OSM Location dots}} -- In general the css output from the two formats will be identical, but the the concise version will allow bits of -- greater control over some of the settings. -- see the documentation on the two template pages for details of how to use the mapping features. -- If language customisation is needed, there are text items below that can be translated. Also see the color table -- below with details of how to add additional color names to allow localised alternatives. -- (Translating other language shape-types could be possible, but has not currently been contemplated. -- Parameter name translation would be harder but likely to be possible, ideally still retaining compatibility -- with template calls already written using English). local negativeAnswer={no=1,'0'-1,off=1} local fullscreenlinktext='Click for interactive fullscreen map with links to nearby articles' local toggletext='[Hide/show caption list]' local termsOfUse='Maps: terms of use' local aboutOSM='About OpenStreetMaps' local shapeList={} --This sets up the 'factoryDefault' shape group 0 shapeList["0"]={shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border of the shape outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items text settings for labels textSZ="11px", textCL="darkgrey", textNG="0deg", --sgf further text settings textSP="0px", textLH="120%", textOL="0px", textBG="transparent", --sgd items for dotTag text settings tagSize="10px", tagColor="white", tagSpacer="0px", tagAngle="0deg", --sge items for extension line to connect label to dot textEW="0px", textEC="darkgrey", textES="solid" } local colorList={} -- used by colorLookup to catch unsupported colors (eg 'LimeGreen'), to convert to generic version colorList['green']='hardgreen' -- it could also be added to to include alternative language equivelants, for a quick solution. colorList['red']='hardred' -- colorList ['source'] = target colorList['white']='white' -- converts any color that includes 'source' into its equivelent target colorList['blue']='hardblue' -- note, for translation you can add to this list, rather than replace it, colorList['brown']='brown' -- which would mean existing map definitions in english would also still work, alongside translated ones colorList['grey']='hardgrey' colorList['gray']='hardgrey' colorList['purple']='hardpurple' colorList['orange']='hardorange' colorList['leaf']='hardleaf' --for a more thorough translation, you can add all the variants of the colors as further CTB elements and hex values or redirects local CTB={} -- set up a table of color names (the CTB Color table index) and html hash colorhex values. CTB["paleblue"],CTB["softblue"],CTB["hardblue"],CTB["darkblue"]="#D6E1EC","#77A1CB","#4B77D6","#1c559e" CTB["palered"],CTB["softred"],CTB["hardred"],CTB["darkred"] = "#FCC6C0","#EC644B","#DB3123","#AA1205" CTB["palegreen"],CTB["softgreen"],CTB["hardgreen"],CTB["darkgreen"]= "#D2F0E5","#81AF81","#269F46","#0b7527" CTB["paleleaf"],CTB["softleaf"],CTB["hardleaf"],CTB["darkleaf"]= "#dff5c1","#b5e376","#8cc244","#679c21" CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"]= "#E8E8D6","#AAAA88","#777755","#444433" CTB["palegray"],CTB["softgray"],CTB["hardgray"],CTB["darkgray"]=CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"] CTB["palebrown"],CTB["softbrown"],CTB["hardbrown"],CTB["darkbrown"]="#FAF6ED","#CCB56C","#AD7F14","#754910" CTB["palepurple"],CTB["softpurple"],CTB["hardpurple"],CTB["darkpurple"]="#e0d1e6","#c784e0","#a029cf","#7a05a8" CTB["paleorange"],CTB["softorange"],CTB["hardorange"],CTB["darkorange"]="#ffedc2","#ffcf61","#EEB533","#e39f05" CTB["black"],CTB["white"],CTB["yellow"]="#000000","#FFFFFF","#FAF039" CTB["background"],CTB["paleground"],CTB["beigeground"]="#f9f6f2","#FEFEFA","#F5F5DC" CTB["beige"]=CTB["beigeground"] CTB["aqua"],CTB["teal"],CTB["fuchsia"] = "#00FFFF","#008080","#FF00FF" CTB["maroon"],CTB["olive"],CTB["navy"] = "#800000","#808000","#000080" CTB["lime"],CTB["limegreen"],CTB["aquamarine"] = "#00FF00","#32CD32","#7FFFD4" CTB["silver"],CTB["yellow"],CTB["orchid"] = "#800000","#FFFF00","#DA70D6" -- set up a table of predefined clip-paths local pathshape={} pathshape.squaredd = "M 19,1.25 l 0,18 -18,0 0,-18 18,0m-1,1 -16,0 0,16 16,0 0,-16m-1,1 0,14 -14,0 0,-14 14,0zm-1,1 -12,0 0,12 12,0 0,-12zm-1,1 0,10 -10,0 0,-10 10,0z" pathshape.squared = "M 18,2.5 l 0,15 -15,0 0,-15 15,0m-1,1 -13,0 0,13 13,0 0,-13zm-1,1 0,11 -11,0 0,-11 11,0z" pathshape.triangledd="M 0 20,20 20,10 0,0 20ZM1.5 19,10 1.7,18.5 19,1.5 19ZM3 18,17 18,10 3.8,3 18ZM4.5 17,10 5.4,15.4 17, 4.5,17ZM6 16,13.8 16,10 7.4z" pathshape.triangled ="M1,18 l 18,0 l -9,-18 l -9,18zm1.7,-1.1 l 7.3,-14.6 l 7.3,14.6 l -14.6, 0zm1.7,-1 l 11.0,0 l -5.5,-11 l -5.5,11z" pathshape.circledd = "M0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm0.8,0a9.2,9.2 0 1,1 18.4,0a9.2,9.2 0 1,1 -18.4,0m1,0a8.2,8.2 0 1,0 16.4,0a8.2,8.2 0 1,0 -16.4,0zm0.8,0a7.2,7.2 0 1,1 14.8,0a7.2,7.2 0 1,1 -14.8,0m1,0 a6.4,6.4 0 1,1 12.8,0a6.4,6.4 0 1,1 -12.8,0z" pathshape.circled = "M2.5,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0m0.8,0 a5,5 0 1,1 11.4,0a5,5 0 1,1 -11.4,0" pathshape.diamond = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10z" pathshape.diamondd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm1,0 l 6,8.5 l 6,-8.5 -6,-8.5 -6,8.5zm1,0 l 5,-7 5,7 -5,7 -5,-7z" pathshape.diamonddd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm0.75,0 l 6.25,9 l 6.25,-9 -6.25,-9 -6.25,9zm0.75,0 l 5.5,-8 5.5,8 -5.5,8 -5.5,-8zm0.75,0 l 4.75,7 l 4.75,-7 -4.75,-7 -4.75,7zm0.75,0 l 4,-6 4,6 -4,6 -4,-6z" pathshape.crossd = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0zM2.3,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.cross = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0z" pathshape.thincross = "M2,12 l6,0 l0,6 l4,0 l0,-6 l6,0 l0,-4 l-6,0 l0,-6 l-4,0 l0,6 l-6,0z" pathshape.fivepointstar = "M10 0 L12.245 6.91 19.511 6.91 13.633 11.18 15.878 18.09 10 13.82 4.122 18.09 6.367 11.18 0.489 6.91 7.755 6.91Z" pathshape.fivepointstard = "M10 1.5 L 11.90825 7.3735 18.08435 7.3735 13.08805 11.003 14.9963 16.8765 10 13.247 5.0037 16.8765 6.91195 11.003 1.91565 7.3735 8.09175 7.3735 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sixpointstar = "M10 0 L12.323 5.977 18.66 5 14.645 10 18.66 15 12.323 14.023 10 20 7.677 14.023 1.34 15 5.355 10 1.34 5 7.677 5.977Z" pathshape.sixpointstard = "M10 1.5 L 11.97455 6.58045 17.361 5.75 13.94825 10 17.361 14.25 11.97455 13.41955 10 18.5 8.02545 13.41955 2.639 14.25 6.05175 10 2.639 5.75 8.02545 6.58045 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sevenpointstar = "M10 0 L12.048 5.747 17.818 3.765 14.602 8.95 19.749 12.225 13.69 12.943 14.339 19.01 10 14.72 5.661 19.01 6.31 12.943 0.251 12.225 5.398 8.95 2.182 3.765 7.952 5.747Z" pathshape.sevenpointstard = "M10 1.5 L11.7408 6.38495 16.6453 4.70025 13.9117 9.1075 18.28665 11.89125 13.1365 12.50155 13.68815 17.6585 10 14.012 6.31185 17.6585 6.8635 12.50155 1.71335 11.89125 6.0883 9.1075 3.3547 4.70025 8.2592 6.38495 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.eightpointstar = "M10 0 L11.88 5.46 17.071 2.929 14.54 8.12 20 10 14.54 11.88 17.071 17.071 11.88 14.54 10 20 8.12 14.54 2.929 17.071 5.46 11.88 0 10 5.46 8.12 2.929 2.929 8.12 5.46Z" pathshape.eightpointstard = "M10 0 L10 1.5 L11.598 6.141 16.01035 3.98965 13.859 8.402 18.5 10 13.859 11.598 16.01035 16.01035 11.598 13.859 10 18.5 8.402 13.859 3.98965 16.01035 6.141 11.598 1.5 10 6.141 8.402 3.98965 3.98965 8.402 6.141ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.ring="M2.6,9.5a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.boxd=pathshape.squared pathshape.boxdd=pathshape.squaredd pathshape.ellipsed=pathshape.circled pathshape.ellipsedd=pathshape.circledd local msg={} local function debugmsg(txt) table.insert(msg,txt) end local pmsg={} local function previewMsg(txt) table.insert(pmsg,txt) end local function colorLookup(color) for c,l in pairs(colorList) do if string.find(color,c) then return l end end return color end local function getColor (color,chk) local c local opacity="100" if not color or color=='' then color='hardgrey' end if color=="transparent" then return color end if color=="background1" then color='background' end if string.byte(color,1,1)==35 and (#color == 7 or #color == 9) then c=color elseif string.byte(color,1,1)==35 and #color == 4 then c=string.sub(color,1,2)..'f'..string.sub(color,3,3)..'f'..string.sub(color,4,4)..'f' else local s=color..'1' s= s:sub(0,s:find("%d")-1) opacity=string.match(color,"%d+") if not CTB[s] then s= colorList[s] end -- check for synonyms and translations if not CTB[s] then debugmsg(mw.addWarning('color = '..color..'. The color name is not defined. Used default grey instead')) end c=CTB[s] or CTB.hardgrey end if opacity and (tonumber(opacity) < 100) and string.find(c,"#")==1 and string.len(c)==7 and opacity~="" then local hexval=string.format("%x",(math.floor(tonumber(opacity)*2.55))) c=c..hexval end return c end function p.colorvalue(frame) -- enable external access to the CTB colorTable values. usage: {{#invoke:OSM Location map|colorvalue|color=hard blue}} local c if not frame.args.color or frame.args.color=='' then c='grey' else c=string.lower(string.gsub(frame.args.color,'%s+','')) end return string.upper(string.sub(getColor(c),2)) end local function checkColors(color) local c=getColor(color,'check') local opacity =1 -- calculate colour brightness and return black or white for contrast if c=='transparent' then return c,'#000000',0 end if not (string.find(c,'#')==1) then return c,'#FFFFFF',0 end if #c>8 then opacity= tonumber('0x'..(string.sub(c,8,9)))/500 end local r=tonumber('0x'..(string.sub(c,2,3)))/255 local g=tonumber('0x'..(string.sub(c,4,5)))/255 local b=tonumber('0x'..(string.sub(c,6,7)))/255 if 0.2126 * r + 0.7152 * g + 0.0722 * b / opacity < 0.7 then return c,'#FFFFFF',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity else return c,'#000000',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity end end local function morethan(a,b) a = tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]') or '0') b = tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]') or '0') return a>b end local function lessthan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) b = tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]') or '0') end return a<b end local function getsize(size) --size1 is between 1 and 3 values, each with px, equating to width,height,corner-rounding --eg '15px 25px 5px' (spaces are optional) or '18px'. returns three numbers local sizeval = {} for v in string.gmatch(size, "[^px]+") do table.insert(sizeval,v) end sizeval[1] = tonumber(sizeval[1]) or 13 sizeval[2] = tonumber(sizeval[2]) or sizeval[1] sizeval[3] = tonumber(sizeval[3]) or 0 return sizeval[1],sizeval[2],sizeval[3] end local function coord2text(coord) -- looks through the output from {{coord}} to find the lat and long decimal values -- and converts compass points to minus or not-minus, return with separating comma. local lat = string.match(coord,'[%.%d]+°[NS]') local lon = string.match(coord,'[%.%d]+°[EW]') local neg={N="",S="-",W="-",E=""} return neg[string.match(lat, '[NS]')]..string.match(lat,'[%.%d]+')..","..neg[string.match(lon, '[EW]')]..string.match(lon,'[%.%d]+') end local function convertCoordsTrad (row) local coords='' if row and string.find (row,'<span class="geo">') then local a,b=string.find(row,'<span class="geo">') local start=b+1 a,b=string.find(row,"</span>",b) local finish=a-1 coords=string.sub(row,start,finish) coords=string.gsub(coords,'; ',',') end return coords end local function convertCoords (row) local start,finish,lat,lon,coords,says if row then local a,b=string.find(row,"<span class=") start=a while a do -- find the final span> finish=b a,b=string.find(row,"span>",b) end if start then coords= string.sub(row,start,finish) says="" if string.find(coords,'<span class="error">') then error("coord error: badly formed coordinates",0) end coords=coord2text(coords) coords = string.sub(row,1,start-1)..coords..string.sub(row,finish+1) else coords=row end return coords else return "Nothing to see here" end end local function fillCommas(val,max) local line='' if not val then line=',' -- ensure there is some content else line = val --string.lower(string.gsub(val,"%s+","")) -- or strip spaces end if string.find(line,',') == 1 then line=' '..line end -- ensure initial comma is not skipped local _, count=string.gsub(line,",","") -- add enough subsequent commas for all entries line=line..string.rep(',',max-count) while(string.find(line,",,") ) do line=string.gsub(line,",,",", ,") --ensure string.gmatch doesn't ignore any empty items by padding with spaces end return line end local function makeLinkBox(left,top,wid,label, link) local linkBoxName='Transparent square.svg' if visibleLinks or '' =='yes' then linkBoxName='Red hollow square.svg' end local builder = mw.html.create('div') --display:inline-block; builder :cssText('position:absolute;left:'..tostring(left-1-wid/2)..'px;top:'..tostring(top-1 + math.min(wid/2-12,0) - wid/2)..'px') :wikitext(string.format( '[[File:%s|%dpx|link=%s|%s]]', linkBoxName, wid+2, link, label )) return tostring(builder) end local function extractItem(row,searchItem) -- remove text following a searchItem or start of line, which might be in quote-marks to allow commas local xend,xstart=1,0 if not row then return '','' end if searchItem then xend,xstart= string.find(row or '',searchItem or 'image:') end if not xstart then return string.gsub(string.gsub(row or '',"%b\"\"", ''),"%b\'\'", '') or '','' end while row:byte(xstart+1) == 32 and xstart<#row do -- skip over any leading spaces xstart=xstart+1 end local xbyte=row:byte(xstart+1) if xbyte == 34 or xbyte == 39 then -- are they wrapped in single or double quotes xstart=xstart+1 xend=row:find(string.char(xbyte),xstart+1) else xend = row:find(',',xstart+1) -- if no quotes, we assume no commas if not xend then xend=#row+1 end end -- return residual row and extracted text return row:sub(0,xstart)..row:sub(xend), row:sub(xstart+1,xend-1) end local function itemCheck(item,ext) if not item then return nil end if not ext then ext='' end return (string.match(item,"[%.%-?%d]+") or '0')..ext end local function stripdivs(line) return string.gsub(line or '',"%b<>", ' ') end local function splitItem(item,max) -- takes a commas-sep list and returns a table of lowercase items with no spaces, or nil local r={} local x=1 item=string.lower(fillCommas(item,max)) for t in string.gmatch(item,"[^,]+") do r[x]=string.gsub(t,"%s+","") if r[x]=='' then r[x]= nil else -- residual items might have commas if x>max then r[max]=(r[max] or '')..', '..r[x] end end x=x+1 end return r end local function ParseShapeTypes (result,args,sgval) -- for use with compressed, comma-separated 'sg plus dots' parameters --[[ shape table items and default values as set at top of page shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items label text textSZ="11px", textCL="white", textNG="0deg", textAT=attributes ("bold" and/or "italic") --sgd items for dotTag tagSize="11px", tagColor="darkgrey", tagSpacer="0px", tagAngle="0deg", --sge extension line textEW="1px", textEC="darkblue", textES="solid" --sgf fx for text labels textSP="0px" textLH="100%" textOL="1px", textBG="paleground", <!--| sga = shape,Size,Color,Angle|sgb= outlineWidth,color,style eg: sga1=circle,14px,blue,0deg| sgb1=0px,dark grey,solid | sgc=textSize,color,angle,bold/italic | sgd=tagSize,Color,Spacer,Angle eg: sgc1=11px,dark grey,0deg,normal| sgd1=9px,white,0px,0deg | sge=lineWidth,color,style |sgf=textspacing,lineHeight%,outlinepx,backgroundcolor, [bold,italic] eg: sge1=0px,black, solid| sgf1=0px,120%,0px,background | dot=shape-group/lat/lon/title/dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> --]] if args["sgn"..sgval] then local sgname=string.match(args["sgn"..sgval],"(%w+)(.*)") sgNames[sgname]=sgval end local parent= args["sgp"..sgval] if parent then parent=string.match(parent,"(%w+)(.*)") local pos= string.find(parent,"%d+") if pos == 1 then parent=string.match(parent,"%d+") else parent=sgNames[parent] or '1' end end if sgval~='H' then if not parent or tonumber(parent) > tonumber(sgval) then if sgval=="1" then parent="0" else parent="1" end end end local itemTab, line, filename result[sgval]={} result[sgval].shapeType=sgval line,filename=extractItem(args['sga'..sgval] or '','image:') if sgval=='1' and not args.sga1 then line='circle,12px,blue,0deg' end -- ensure there is a parent=1 sga result[sgval].shapeFile=filename or '' -- sga= Attributes for shape itemTab=splitItem(line,4) result[sgval].shape = itemTab[1] or result[parent].shape result[sgval].shapeSize=itemTab[2] or result[parent].shapeSize result[sgval].shapeColor=itemTab[3] or result[parent].shapeColor result[sgval].shapeAngle=itemCheck(itemTab[4],'deg') or result[parent].shapeAngle -- sgb= Border outline attributes for shape itemTab=splitItem(args['sgb'..sgval],3) result[sgval].outlineWidth=itemCheck(itemTab[1],'px') or result[parent].outlineWidth result[sgval].outlineColor=itemTab[2] or result[parent].outlineColor result[sgval].outlineStyle=itemTab[3] or result[parent].outlineStyle --sgc=character attributes for label itemTab=splitItem(args['sgc'..sgval],4) result[sgval].textSZ=itemCheck(itemTab[1],'px') or result[parent].textSZ -- size of text in px result[sgval].textCL=itemTab[2] or result[parent].textCL -- colour for text result[sgval].textNG=itemCheck(itemTab[3],'deg') or result[parent].textNG -- Angle for text result[sgval].textAT=itemTab[4] or result[parent].textAT -- attributes bold, and/or italic --sgd=dotTag attributes itemTab=splitItem(args['sgd'..sgval],4) result[sgval].tagSize=itemCheck(itemTab[1],'px') or result[parent].tagSize result[sgval].tagColor=itemTab[2] or result[parent].tagColor result[sgval].tagSpacer=itemCheck(itemTab[3],'px') or result[parent].tagSpacer result[sgval].tagAngle=itemCheck(itemTab[4],'deg') or result[parent].tagAngle --sge= extension line attributes itemTab=splitItem(args['sge'..sgval],4) result[sgval].textEW=itemCheck(itemTab[1],'px') or result[parent].textEW -- width result[sgval].textEC=itemTab[2] or result[parent].textEC -- colour result[sgval].textES=itemTab[3] or result[parent].textES -- style --sgf= fx for label text itemTab=splitItem(args['sgf'..sgval],4) result[sgval].textSP=itemCheck(itemTab[1],'px') or result[parent].textSP -- spacing value for letters result[sgval].textLH=itemCheck(itemTab[2],'%') or result[parent].textLH -- Angle for text result[sgval].textOL=itemCheck(itemTab[3],'px') or result[parent].textOL -- width of text-border line result[sgval].textBG=itemTab[4] or result[parent].textBG -- color for text background return result end local function round(x,dec) -- x=number [, dec=integer] returns numeric value with upto dec decimals (all but first trailing zeros get truncated) if (dec or 0)==0 then return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5) --this avoids .0 where dec=0 end dec =10^(dec) return x>=0 and math.floor(x*dec+0.5)/dec or math.ceil(x*dec-0.5)/dec end local function maptogrid(t,r) --[[ converts mercator projection longitude and latitude coordinates to x and y pixel coordinates, within a frame of given size, centre coordinates and zoom level. t is a table of named indices: {lon, lat, lonbase, latbase, width, height, zoom} output is two values, x and y, rounded to r decimal places--]] local x,y x=t.width/2 + ( ((math.rad(t.lon)*6378137) - (math.rad(t.lonbase)* 6378137) ) / (156543.03*math.cos(t.latbase/180)/(2^t.zoom) ) )*(1-(0.075*(math.abs(t.latbase)/90))) y=t.height/2 + ( ( (math.log(math.tan(math.rad(t.latbase)/2+math.pi/4))*6378137) - (math.log(math.tan(math.rad(t.lat)/2+math.pi/4))*6378137) ) / (156543.03*math.cos(t.latbase/180) / (2^t.zoom) ) )*(1-(0.075*(math.abs(t.latbase)/90))) return round(x,r),round(y,r) --source: python code at https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames and https://wiki.openstreetmap.org/wiki/Mercator --[[ width and height are the size, in pixels, of the map, which will be centerd around lonbase,latbase. Method: Convert lon and lonbase to meter-offsets from coord(0,0), and subtract lonbase from lon, zoom and latbase are used to scale the resulting meter-offset to pixels, and add it to width/2. Convert lat and lat-base to meter-offsets from coord(0,0), subtract lat from latbase, scale the resulting meter-offset to pixels, add it to height/2. A correction factor '*(1-(0.075*(math.abs(t.latbase)/90) ) )' compensates for an error that seems to creep in towards the edges of the map at higher latitudes. It was identified experimentally, and ensures a dot at the edge is in the same place as if that location is positioned at the centre. Original Python code for lat,lon to x,y where 0,0 is the centre of the map print('x=',width+(((math.radians(lon1) * 6378137)- (math.radians(lonbase) * 6378137))/ (156543.03*math.cos(latbase/180)/(2**zoom))),' y=',height+(( (math.log(math.tan(math.pi / 4 + math.radians(latbase) / 2)) * 6378137)- (math.log(math.tan(math.pi / 4 + math.radians(lat1) / 2)) * 6378137))/ (156543.03*math.cos(latbase/180)/(2**zoom))) ) --]] end local function getScale(zoom, lat,magVal) if magVal and magVal>1 and magVal <=2 then zoom=zoom+(magVal-1) end local dist=(156543.03 * math.cos(math.rad(math.abs(lat))) / (2 ^ zoom))/17 local y if dist < 1 then y=(round(dist*10,1)) return tostring(y*100)..'m', tostring(round(y*109,0))..'yds' elseif dist <18 then y=(round(dist,0)) return tostring(y)..'km', tostring(round(y*0.621371,1))..'miles' elseif dist <500 then y=round(dist/10) return tostring(y*10)..'km', tostring(round(y*6.21371,0))..'miles' else y=round(dist/100) return tostring(y*100)..'km', tostring(round(y*62.1371,0))..'miles' end end local function ParseData (args,dotval) -- for use with compressed, comma-separated 'sg plus dots' parameters -- takes a structured series of comma-separated items which get parsed as the following: -- dot(n)= (sgNumber or Name),{{coord}} or (lat,lon), (dotTag) -- dotlink(n) = single-parameter text to give wikilink and/or title used by tootlip, fullscreen dots and autocaption list -- dotlabel(n) = 'label text',pos(left,roght,top,bottom,centre,auto),(dx), (dy) pixel offsets, params, info -- dotpic(n) = single parameter wikimedia filename for an image to use in photopanel and/or fullscreen dots -- dotfeature(n)= 'mark-line' (,linewidth,style,gap) or 'photo-panel' (,image-dim,panel-width,panel-height), draws line to n-1 -- label is used if either a position and/or an x,y offset are not 0,0 ( if no label then dotTag will be put at at the x,y offset, or over the dot -- label text can be autoaligned if x,y puts it left or right of the dot, or centered if above/below) -- quote marks are not needed unless including commas within the label text -- param1 is optional items separated by spaces, and can include [nolabel nolist nomap hemisphere+1 hemisphere-1] -- info is free wikitext, to be used in the fullscreen dot box. (use dotpic to show a picture) --<!--| dotx=shape-group,[lat,lon or {{coord}} ], dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> local result={} local count=1 local row = convertCoords (args["dot"..dotval]) -- swap in any {{coord}} values so they are csv lat and lon row=fillCommas(row,4) result.code=dotval -- store the parameter name as the id code for item in string.gmatch(row,"[^,]+") do -- iterate through 'row', adding each csv item in turn, if present if count==1 then --see if it is a number or a name local pos= string.find(item,"%d+") if pos == 1 then result.group=string.lower(string.gsub(item,"%s+","")) else item=string.match(item,"(%w+)(.*)") -- ensure just a single word result.group=sgNames[item] end elseif count==2 then result.lat=tonumber(string.match(item,"[%.%-?%d]+")) or 0-- find the number, with no non-numeric stuff elseif count==3 then result.lon=tonumber(string.match(item,"[%.%-?%d]+")) or 0 elseif count==4 then result.dotTag=item:match( "^%s*(.-)%s*$" ) or "" -- dotTag allows for internal spaces, but no commas end count=count+1 end row, result.labelText= extractItem(args["dotlabel"..dotval]) result.labelText= string.gsub(result.labelText,"[%^]+","<br>") -- convert hats to line breaks local item=splitItem(row,6) result.labelPos=item[2] or 'center' result.dx=tonumber(string.match(item[3] or '0',"[%.%-?%d]+")) or 0 result.dy=tonumber(string.match(item[4] or '0',"[%.%-?%d]+")) or 0 result.param1=string.lower(item[5] or '') if string.find(result.param1,"hemisphere-1",1,true) then result.lon=result.lon-360 elseif string.find(result.param1,"hemisphere+1",1,true) then result.lon=result.lon+360 end local txt = '' if item[6] then -- ensure all info elements are included, including commas count=1 local max=6 for t1 in string.gmatch(fillCommas(row,max),"[^,]+") do if count>max then txt=txt..',' end if count>=max then txt=txt..t1 end count=count+1 end end result.info=txt result.imageName = args['dotpic'..dotval] -- Get first wikilinked item (if any) from the args.dotlink and set this plus the delinked text local testx=args["dotlink"..dotval] or '' result.dotLink=testx if testx ~= '' then testx=stripdivs(testx) result.title=delink({ testx }) local linkstart= string.find(testx,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then result.dlink=delink( { string.sub(testx,linkstart,string.find(testx,']]',1,true)+1),wikilinks='target' } ) else result.dlink='' end else result.dlink='' result.title='' end if args['dotfeature'..dotval] then local item=splitItem(args['dotfeature'..dotval],6) if (item[1] or '') =='photo-panel' then result.ppwidth= tonumber(string.match((item[3] or '110'),"%d+")) -- panel width result.ppheight= tonumber(string.match((item[4] or '48'),"%d+") ) --panel height result.photowidth=round(tonumber(string.match((item[2] or '1.3'),"[%.%-?%d]+")) * result.ppheight+1,0) -- dimension to set image size result.photoImage=result.imageName result.posType='photo-panel' elseif (item[1] or '') == 'mark-line' then local x=tonumber(dotval or '0') result.markDest=item[5] or tostring(x-1) result.mlWidth= tonumber(string.match((item[2]) or '',"%d+") or '1') result.mlStyle= item[3] or 'solid' result.mlGap=tonumber(string.match((item[4] or ''),"[%d]+") or '0') result.posType='mark-line' -- debugmsg('making line for '..tostring(x)..'to '..tostring(result.markDest)..' with width '..tostring(result.mlWidth)) end --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end maplist.lon=result.lon maplist.lat=result.lat result.gridx, result.gridy = maptogrid(maplist,1) -- convert geo coords to grid xy - using values from maplist table return result end local function multiCheck (args, argName, argVal, defVal, alt) if not alt then alt='nonexistant' end if argVal=='H' and not args[argName..'H'] then argVal=highlightNum or '1' end if argVal=='' then return (args[argName] or args[alt] or (args[argName..'D']) or args[alt..'D'] or defVal) -- unnumbered args do not inherit from D or 1 else return (args[argName..argVal]) or (args[alt..argVal]) or (args[argName..'D']) or (args[alt..'D']) or (args[argName..'1']) or (args[alt..'1']) or defVal end end local function assignTradstyleShape(shapeResult,default,dotResult,args,nval) local item,itemTab local autoDotTag='' local shapeWidth,shapeHeight=0,0 local argval=nval -- to catch the unnumbered shape series if argval=='0' then argval='' end if nval=='H' then shapeResult.H={} end item=string.lower(multiCheck(args,'shape',argval,'image')) if string.find(item,'n-',0,true)==1 or string.find(item,'l-',0,true)==1 then autoDotTag=string.sub(item,0,1) item=string.sub(item,3) end if item == 'image' then shapeResult[nval].shape = 'image:' shapeResult[nval].shapeFile =multiCheck(args,'mark',argval,'Red pog.svg') shapeWidth=-1 else shapeResult[nval].shape = item end item= multiCheck(args,'mark-size',argval,'14px') local a,b,c= getsize(string.gsub(string.gsub(item,',','px')..'px','pxpx','px')) if b==a and args['mark-dim'..argval] then b= b / tonumber(string.match(args['mark-dim'..argval],"[%.%-?%d]+")) end shapeHeight=b/2 item=tostring(a)..'px'..tostring(b)..'px'..tostring(c)..'px' shapeResult[nval].shapeSize= item itemTab=splitItem(multiCheck(args,'shape-color',argval,'hard red'),2) shapeResult[nval].shapeColor=itemTab[1] or 'hardred' item=itemCheck(itemTab[2],'%') -- jump through the various opacity hoops and add to color if needed if not item then item=itemCheck(args['shape-opacity'..argval],'%') end if item and item~='0%' and item~='100%' then shapeResult[nval].shapeColor=shapeResult[nval].shapeColor..item end shapeResult[nval].shapeAngle=itemCheck(multiCheck(args,'shape-angle',argval,'0'),'deg') or '0deg' --sort out the outline entry itemTab=splitItem(multiCheck(args,'shape-outline',argval,'transparent,0,100,solid'),4) shapeResult[nval].outlineColor=itemTab[1] or 'dark grey' shapeResult[nval].outlineWidth=itemCheck(itemTab[2],'px') or '1px' if itemTab[3] and itemTab[3]~='100' and itemTab[3]~='0' then shapeResult[nval].outlineColor=shapeResult[nval].outlineColor..itemCheck(itemTab[3],'%') end shapeResult[nval].outlineStyle=itemTab[4] or 'solid' -- label size, background, outline itemTab=splitItem( multiCheck(args,'label-size',argval,'12'),3) shapeResult[nval].textSZ=itemCheck(itemTab[1],'px') or '12px' if itemTab[2]=='outline' then shapeResult[nval].textBG=itemTab[3] or 'transparent' shapeResult[nval].textOL='1px' elseif itemTab[3]=='outline' then shapeResult[nval].textBG=itemTab[2] or 'transparent' shapeResult[nval].textOL='1px' else shapeResult[nval].textOL='0px' shapeResult[nval].textBG=itemTab[2] or 'transparent' end if getColor(shapeResult[nval].textBG)==CTB['hardgrey'] and shapeResult[nval].textBG~='hardgrey' then shapeResult[nval].textBG= 'transparent' end --label color etc itemTab=splitItem(multiCheck(args,'label-color',argval, 'darkgrey','label-colour'),2) shapeResult[nval].textCL=itemTab[1] or 'darkgrey' if itemTab[2] and itemTab[2]~='0%' and itemTab[2]~='100%' then shapeResult[nval].textCL=shapeResult[nval].textCL..itemTab[2] end shapeResult[nval].textSP=itemCheck( multiCheck(args,'label-spacing',argval,'0'),'px') -- sets letter-spacing in px shapeResult[nval].textLH=itemCheck( multiCheck(args,'label-height',argval,'120'),'%') -- sets line height, 120% default shapeResult[nval].textNG=itemCheck(multiCheck(args,'label-angle',argval,'0'),'deg') --sgd=dotTag attributes shapeResult[nval].tagSize=tostring(shapeHeight*1.5)..'px' local c1,c2=checkColors(shapeResult[nval].shapeColor) shapeResult[nval].tagColor=c2 shapeResult[nval].tagSpacer='0px' shapeResult[nval].tagAngle='0deg' -- sge extension line attributes local shapePos=splitItem(multiCheck(args,'label-pos',argval,'right'),6) if shapePos[2]=='with-line' or shapePos[2]=='n-line' then shapeResult[nval].textEW=(shapePos[3] or '1')..'px' -- width shapeResult[nval].textEC=(shapePos[4] or shapeResult[nval].shapeColor or 'darkgrey') elseif shapePos[2]=='photo-panel' then shapeResult[nval].textEW='2px' -- width shapeResult[nval].textEC=shapeResult[nval].textCL else shapeResult[nval].textEW='0px' -- width shapeResult[nval].textEC='grey'-- colour end shapeResult[nval].textES='solid' if argval=='H' then return dotResult end --Assign dot values local dotItem={} dotItem.group=nval dotItem.code=nval dotItem.posType=shapePos[2] or 'nil' if (shapePos[2] or '') =='photo-panel' then dotItem.ppwidth= tonumber(string.match((shapePos[4] or '110'),"%d+")) dotItem.ppheight= tonumber(string.match((shapePos[5] or '48'),"%d+") ) dotItem.photowidth=round(tonumber(string.match((shapePos[3] or '1.3'),"[%.%-?%d]+")) * dotItem.ppheight+1,0) dotItem.photoImage=args['mark-image'..argval] --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end if (shapePos[2] or '') =='mark-line' then local x=tonumber(argval or '1') dotItem.markDest=shapePos[6] or tostring(x-1) dotItem.mlWidth= tonumber(string.match((shapePos[3] or '1'),"%d+")) dotItem.mlStyle= shapePos[4] or 'solid' dotItem.mlGap=tonumber(string.match((shapePos[5] or '0'),"[%d]+")) shapeResult[nval].textEC=shapeResult[nval].outlineColor or 'darkgrey' end if args['mark-coord'..argval] then itemTab=splitItem(convertCoordsTrad (args['mark-coord'..argval]),2) dotItem.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) or 0 else dotItem.lat=tonumber(string.match(args['mark-lat'..argval],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(args['mark-lon'..argval],"[%.%-?%d]+")) or 0 end if args['dateline'..argval] and (args['dateline'..argval]=='1' or args['dateline'..argval]=='-1') then dotItem.lon=dotItem.lon+(tonumber(args['dateline'..argval] ) *360) end maplist.lon=dotItem.lon maplist.lat=dotItem.lat dotItem.gridx, dotItem.gridy = maptogrid(maplist,1) local item=args['mark-title'..argval] or '' -- sort out the caption, wikilink and plaintext tooltip items from dotLink if item=='none' then dotItem.param1='nomap nolist' item='' end dotItem.dotLink=item if item ~= '' then item=stripdivs(item) dotItem.title=delink({item}) local linkstart= string.find(item,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then dotItem.dlink=delink({string.sub(item,linkstart,string.find(item,']]',1,true)+1),wikilinks='target'}) else dotItem.dlink='' end else dotItem.dlink='' dotItem.title='' end if autoDotTag=='n' then item=nval elseif autoDotTag=='l' then item=string.char(64+tonumber(nval)) else item='' end dotItem.dotTag = args['numbered'..argval] or item if shapePos[2]=='n-line' and (args['label'..argval] or args['label'..argval]=='') then if dotItem.dlink =='' then item=dotItem.dotTag..' '..args['label'..argval] else item='[['..dotItem.dlink..'|'..dotItem.dotTag..']] '..args['label'..argval] end else item=(args['label'..argval] or '') end if args['labela'..argval] then item = item..'^'..args['labela'..argval] end if args['labelb'..argval] then item = item..'^'..args['labelb'..argval] end local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end dotItem.labelText = a -- convert hats to line breaks if argval=='' then item = (args['label-offset-x']) or (args.ldx) or '0' else item=args['label-offset-x'..argval] or args['ldx'..argval] or args['label-offset-xD'] or args.ldxD or args['label-offset-x1'] or args.ldx1 or '0' end dotItem.dx=tonumber(string.match(item,"[%.%-?%d]+")) if argval=='' then item = (args['label-offset-y']) or (args.ldy) or '0' else item=args['label-offset-y'..argval] or args['ldy'..argval] or args['label-offset-yD'] or args.ldyD or args['label-offset-y1'] or args.ldy1 or '0' end dotItem.dy=tonumber(string.match(item,"[%.%-?%d]+")) dotItem.labelPos=shapePos[1] if args['mark-image'..argval] then dotItem.imageName= args['mark-image'..argval] end dotItem.info=args['mark-description'..argval] or '' table.insert(dotResult,1,dotItem) -- add to start of list, so they are in reverese order for displaying return dotResult end local function tradstyleParseShapes(args,dotTable,dotmax) local sgNumbers,sgSortable={},{} for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) then sgNumbers[x]=x -- add it to the list end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form TODO this must be possible in a single list! if args['mark-coord'] or (args['mark-lat'] and args['mark-lon']) then table.insert(sgSortable,'0') end -- add it to the end of the list local default={} default.D={} local dotResult={} for k,sgnum in pairs(sgSortable) do -- work through the sorted list, parsing each set of shapes in turn, from 1 upwards shapeList[sgnum]={} dotTable=assignTradstyleShape(shapeList,default,dotTable,args,sgnum) end dotTable=assignTradstyleShape(shapeList,default,dotTable,args,'H') -- construct an extra highlight shapeitem return dotTable end local function checkfortooltip (title,dx,dy,dotlabel,dlink,nolabel) -- returns tlink if available, and dlink, if needed and tshape=true if shape needed local tshape,tlink = false,"" if dlink~='' and not nolabel then tlink=dlink end if (tlink=="" or nolabel) and title~="" then tshape=true end -- tshape flags True if title is wanted for shape if (not (dx==0 or dy==0) or dotlabel=='') and title~='' then tshape=true end -- add tooltip to shape if its number has moved return tshape,tlink end local function tshift(angle) -- adjustment to place text near the centre of a triangle, shifted to allow rotation of triangle shape local x=tonumber(string.match(angle,"%-?%d+")) if x<0 then x=360+x end -- set to a single degree direction, 0 to 360 if x>359 then return 0,0 end -- shift the centre of the triangle based on rotation value if x <=40 or x>=320 then return -0.17,0 -- triangle up= -shiftv elseif x>=140 and x<=220 then return 0.17,0 --triangle down= +shiftv elseif x >220 then return 0,-0.17 --triangle left= -shifth elseif x >40 then return 0,0.22 --triangle right= +shifth end return 0,0 end local function makeTriangle(result,row,shape,outline,tlink) local w,h,r=getsize(shape.shapeSize) if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) w=w+p*2 h=h+p*2 end table.insert(result,'<div ') if tlink then table.insert(result,' title="'..row.title..'" ') end table.insert(result,'style="display:inline-block; position: absolute') if shape.shapeAngle ~= '0deg' then table.insert(result,'; transform: rotate('..shape.shapeAngle..')') end local shiftv,shifth=0,0 shiftv,shifth=tshift(shape.shapeAngle) table.insert(result,'; top: '..tostring(row.gridy-h/2+h*shiftv)..'px') table.insert(result,'; left: '..tostring(row.gridx-w/2+w*shifth)..'px; width: 0; height: 0; outline-width: 0px') table.insert(result,'; border-left: '..tostring(w/2)..'px solid transparent') table.insert(result,'; border-right: '..tostring(w/2)..'px solid transparent') if outline then -- fill with outline colour, to make a 'base layer' or shape colour table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.outlineColor).. '">') else table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.shapeColor).. '">') end table.insert(result,'</div>') end local function makeSquare(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) if shape.shape=='square' then h=w end -- squares are always square!. box can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end if r~=0 then div:css('border-radius',tostring(r).."px") end if shape.shape=='panel' then div:css('top', tostring(row.gridy).."px") div:css('left', tostring(row.gridx).."px") else div:css('top', tostring(row.gridy-h/2).."px") div:css('left', tostring(row.gridx-w/2).."px") end div:css('width', tostring(w).."px") div:css('height', tostring(h).."px") div:css('background-color', getColor(shape.shapeColor) ) div:css('color', 'inherit') table.insert(result,tostring(div)) end local function makeCircle(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding if shape.shape=='circle' then h=w end -- circles are always round. ellipse can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end div :css('top', tostring(row.gridy-h/2).."px") :css('left', tostring(row.gridx-w/2).."px") :css('width', tostring(w).."px") :css('height', tostring(h).."px") :css('border-radius', "50%") :css('background-color', getColor(shape.shapeColor) ) :css('color', 'inherit') table.insert(result,tostring(div)) end local function makeRuleA(result,row,shape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local lineV=0 if shape.shape=='rulea' then lineV=oWid*3+16 end table.insert(result,"<div style=\"display:inline-block; position: absolute") -- create a square transparent container, which will rotate line and arrow together table.insert(result,"; top:"..tostring(row.gridy - w/2).."px") table.insert(result,"; left:"..tostring(row.gridx - w/2).."px") table.insert(result,"; width:"..tostring(w).."px") table.insert(result,"; height:"..tostring(w).."px; background:transparent; color:inherit;") table.insert(result,"; transform: rotate( "..tostring(tonumber(string.match(shape.shapeAngle,"[%.%-?%d]+")) - 90).."deg);\">" ) table.insert(result,"<div style=\"display:inline-block; position: absolute") -- put the line (as a border-right) across the container table.insert(result,"; top:"..tostring(lineV).."px") table.insert(result,"; left:"..tostring((w - oWid )/2).."px; width: 0px") table.insert(result,"; height: "..tostring(w -lineV).."px") table.insert(result,"; border-right: "..shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) table.insert(result,"; background:transparent; color:inherit;\"></div>") if shape.shape=='rulea' then table.insert(result,"<div style=\"display:inline-block; position: absolute; top: 0px") --and add arrow head table.insert(result,"; left:"..tostring(w/2-( oWid/2)-oWid*0.55-2).."px; width: 0; height: 0; outline-width: 0px") table.insert(result,"; border-left: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-right: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-bottom: "..tostring(oWid*3+16).."px solid "..getColor(shape.outlineColor).."\"></div>") end table.insert(result,"</div>") end local function makeCurveA(result,row,shape) -- draw a curve with Arrow ----- local w,h=getsize(shape.shapeSize) -- = width,height local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local Angle=tonumber(string.match(shape.shapeAngle,"[%.%d]+")) table.insert(result,'<div style="position: absolute;') --set up out div, which will allow the whole to rotate table.insert(result,'top:'..tostring(row.gridy - (w + oWid*3+16)/2)..'px;') table.insert(result,'left:'..tostring(row.gridx - ( w + oWid*3+16)/2)..'px; ') table.insert(result,'width: '..tostring(w+oWid*3+16)..'px; ') table.insert(result,'height: '..tostring(w+oWid*3+16)..'px; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate( '..tostring(Angle-120)..'deg);">') else table.insert(result,'transform: rotate( '..tostring(Angle -62)..'deg);">') end table.insert(result,'<div style="position: absolute;') --set up div for the rounded corner of a rectangle table.insert(result,'border-left: '..shape.outlineWidth..' '..shape.outlineStyle..' '..getColor(shape.outlineColor)..';') if shape.shape=='curvea' then table.insert(result,'border-radius: 10000px 0 0 '..tostring(w)..'px; top:0px; left:'..tostring(w*0.25)..'px;') else table.insert(result,'border-radius: '..tostring(w)..'px 0 0 10000px;') table.insert(result,'top:'..tostring((oWid*3+16)/2+w*0.15)..'px; left:'..tostring(w*0.25)..'px;') end -- and add a triangular arrow head table.insert(result,'width: '..tostring(w)..'px; height: '..tostring(w)..'px;"></div><div style="position: absolute; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate(180deg); top:'..tostring(w-1)..'px; ') else table.insert(result,'transform: rotate(0deg); top: '..tostring(0-( ( oWid*3+16)/2)+1+( w*0.15) )..'px;') end -- reverse table.insert(result,'left:'..tostring(0-( oWid*0.6)-2+(w*0.25))..'px;') table.insert(result,'width: 0; height: 0; outline-width: 0px; border-left: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-right: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-bottom: '..tostring(oWid*3+16)..'px solid '..getColor(shape.outlineColor)..';"></div></div>') end local function makeLineTo (result,x1,y1,x2,y2,oWid, oStyle, oCol,double) table.insert(result,"<div style=\"display:inline-block; position: absolute;") -- draw a line between x1,y1 and x2,y2, px-coords where 0,0 is centre of frame -- Maths calculations thanks to ES table.insert(result,"left: "..tostring(x1+( (x2-x1)/2) - (math.sqrt( ( x2-x1)^2 + (y2-y1)^2 )/2)-1).."px;") table.insert(result,"top: "..tostring(y1+( ( y2-y1 )/2) ).."px;") table.insert(result,"width: "..tostring(math.sqrt( (x2-x1 )^2 + ( y2-y1 )^2) ).."px;") table.insert(result,"height: "..tostring(double).."px; background-color:transparent; color:inherit; ") table.insert(result,"outline-width: 0; border-bottom: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) if double>1 then table.insert(result,"border-top: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) end if x1==x2 then table.insert(result,"transform: rotate(90deg);") else table.insert(result,"transform: rotate("..tostring(math.atan(( y2-y1)/( x2-x1 ) )*180/math.pi).."deg);\"></div>") end end local function makeClipPath(result,row,shape,outline,tshape) --tshape is a flag to show if the tooltip (title=) is wanted -- return the text css div code to position and draw a shape occupying a specified clippath if not pathshape[shape.shape] then debugmsg(mw.addWarning('shape'..row.code..' = '..shape.shape..'. The shape name is not defined')) return end local w,h,r=getsize(shape.shapeSize) if string.match(shape.shape,"circle") or string.match(shape.shape,"square") then h=w end -- use ellipse and box for stretched shapes local shifth,shiftv = 0,0 if string.match(shape.shape,"triangle") then shiftv,shifth =tshift(shape.shapeAngle) end if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) or 0 w=w+p*2 h=h+p*2 end table.insert(result,"<div ") if tshape then -- Add tooltip if needed table.insert(result," title=\""..row.title.."\" ") end table.insert(result,"style=\"display:inline-block; position: absolute; background-color:") if outline then table.insert(result,getColor(shape.outlineColor)) -- fill with outline colour, to make a 'base layer' else table.insert(result,getColor(shape.shapeColor)) end table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;"..pathshape[shape.shape].."&#39;) ") -- adds the required clippath data from the table of pathshape string literals table.insert(result,"; top:"..tostring(row.gridy - 10 + h*shiftv).."px") table.insert(result,"; left:"..tostring(row.gridx - 10 + w*shifth).."px") table.insert(result,"; width:20px") -- needs to be a path within a 20px20px box, and then rescales using size values to match other shape sizes table.insert(result,"; height:20px; transform:scale("..tostring(w/16)..", "..tostring(h/16)..")") if shape.shapeAngle ~= "0deg" then table.insert(result," rotate("..shape.shapeAngle..")") end table.insert(result,"\"></div>") end local function makeImage(result,row,shape) local w,h,r=getsize(shape.shapeSize) local image=shape.shapeFile if not image or image=='' then image='Red pog.svg' end local imagediv=mw.html.create ('div') imagediv:css('position', "absolute") if shape.shapeAngle ~= "0deg" then imagediv:css('transform',"rotate("..shape.shapeAngle..")") end imagediv :css('top', (row.gridy-1 + math.min(h/2-12,0) - h/2).."px") --File seems to adjust pos for small images :css('left', (row.gridx-1-w/2).."px") :css('background-color', "transparent" ) :css('color','inherit') :wikitext('[[file:'..image..'|'..tostring(w+2)..'px|alt='..(row.title or '')..'|link=]]') table.insert(result,tostring(imagediv)) end local function makePhotoPanel(result,row,shape) local h=row.ppheight table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;' ) table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') table.insert(result, 'width: '..tostring(row.ppwidth)..'px; height: '..tostring(h)..'px; border-radius: 2px; color:inherit;') table.insert(result, 'background-color: #E8E8D6; outline: 2px solid '..getColor(shape.textCL)..'; box-shadow: 2px 2px 4px #33203335;"></div>') if row.photoImage and row.photowidth >0 then table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;') if row.labelPos=='left' or string.find(row.labelPos,'west') then --debugmsg(row.labelText..', Align=right, ppwidth='..tostring(row.ppwidth)..', photowidth='..row.photowidth..', dx='..tostring(row.dx)..', dy='..tostring(row.dy)) table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') row.dx=row.dx+row.photowidth/2 else --debugmsg(row.labelText..', Align=left, ppwidth='..tostring(row.ppwidth)..', photowidth='..row.photowidth..', dx='..tostring(row.dx)..', dy='..tostring(row.dy)) table.insert(result, 'left: '..tostring(row.gridx+row.dx + (row.ppwidth-row.photowidth) - row.ppwidth/2)..'px;') row.dx=row.dx-row.photowidth/2 end table.insert(result, 'background-color:transparent; color:inherit; border-radius: 2px;">') table.insert(result,'[[File:'..row.photoImage..'|x'..tostring(h)..'px|File:'..row.photoImage..']]</div>') row.labelPos='center' end end local function makePanelText(result, row, shape) local w,h,r=getsize(shape.shapeSize) local ty=tonumber(string.match(shape.textSZ or '11',"%d+") ) table.insert(result,'<div style="position:absolute; line-height: 120%; font-size: '..shape.textSZ..'; color:'..getColor(shape.textCL)..';') if row.labelPos == 'left' or string.find(row.labelPos,'west') then table.insert(result,'top: '..tostring(row.dy+row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+3)..'px; text-align: left; width:'..tostring(w)..'px;') elseif row.labelPos == 'right' or string.find(row.labelPos,'east') then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+w-3)..'px; text-align: right; width: max-content; transform: translateX(-100%);') elseif row.labelPos == 'top' or row.labelPos== 'north' then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') elseif row.labelPos == 'bottom' or row.labelPos=='south' then local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*1.1 table.insert(result,'top: '..tostring(row.gridy+w-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') else -- center or centre local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*0.6 table.insert(result,'top: '..tostring(row.gridy+(h/2)-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') end if shape.textSP and shape.textSP ~='0px' then table.insert(result,"letter-spacing: "..shape.textSP..';') end if shape.textLH and shape.textLH ~='120%' then table.insert(result,"line-height: "..shape.textLH..';') end table.insert(result,"vertical-align: middle;\">"..row.labelText.."</div>") end local function makeTextItem(result, row, shape, align, tlink, textItem, dotItem) local w,h,r=getsize(shape.shapeSize) table.insert(result,"<div ") if row.title ~= "" then table.insert(result," title=\""..row.title.."\" ") end local ty,bry,linkoffset = 0,0,0 local compy=0 local lh=tonumber(string.match(shape.textLH or '120',"%d+"))/100 if dotItem==1 or (dotItem==2 and row.posType~='n-line') then -- if there is a dotTag in the middle of the shape then use the tag settings ty=tonumber(string.match(shape.tagSize,"%d+")) or 0 table.insert(result,"style=\"position:absolute; line-height: 120%; top: "..tostring(row.gridy-ty*0.62)) table.insert(result,"px; left: "..tostring(row.gridx).."px; width: fit-content; ") table.insert(result,"text-align: center; color: "..getColor(shape.tagColor).."; background-color: transparent;") local trf="" if shape.tagAngle ~="0deg" then trf=" rotate("..shape.tagAngle..")" end table.insert(result, "transform: translateX(-50%)"..trf.."; font-size: "..shape.tagSize..";") if shape.tagSpacer~='0px' then table.insert(result, "letter-spacing:"..shape.tagSpacer..";") end else -- or add tfx settings for left, right or center align, colors, backgrounds, border-outline table.insert(result,'style="position:absolute; ') if dotItem==2 then -- dotTag is out at x,y so 85% ty=tonumber(string.match(shape.tagSize or '0',"%d+")) table.insert(result,'font-size: '..shape.tagSize..'; padding:0px 2px;line-height: 85%; top: '..tostring(row.dy+row.gridy-ty*0.52)) else -- it is labelText, so use textLH or 120% ty=tonumber(string.match(shape.textSZ or '11',"%d+")) if row.labelPos=='northwest' or row.labelPos=='northeast' then compy=-ty elseif row.labelPos=='southeast' or row.labelPos=='southwest' then compy=ty/2 end if row.labelPos and not (row.labelPos== 'auto' or row.labelPos=='') then bry=(select(2, string.gsub(row.labelText,"<br>", ""))*lh) -- is it a multiline text? expand by line-height /120%? if row.posType == 'with-line' then bry=0 end if row.labelPos=='bottom' or row.labelPos == 'south' then bry = 0 -- and shift by none, all or half elseif row.labelPos=='top' or row.labelPos == 'north' then if shape.shape=='image:' then bry= 1 + math.min(w/2-10,0)-bry*ty else bry= -bry*ty+2 end else bry=-bry*(ty/2 * lh) end end if row.posType == 'photo-panel' then bry=bry+3 end table.insert(result,'font-size: '..shape.textSZ..'; padding:0px 3px;line-height: '..shape.textLH..'; ') table.insert(result,'top: '..tostring(row.dy+row.gridy+compy+bry-ty*lh/2)) end table.insert(result,"px; left: "..tostring(row.dx+row.gridx).."px; color: "..getColor(shape.textCL).."; ") table.insert(result,"width: max-content; ") local trf="" if shape.textNG ~="0deg" then trf="rotate("..shape.textNG..")" end if shape.textOL~="0px" then table.insert(result,"background-color: "..getColor(shape.textBG).."; ") table.insert(result,"border: "..shape.textOL.." solid "..getColor(shape.textCL).."; border-radius:6px;") else table.insert(result,"background-color: transparent;") end if row.labelPos=="right" or string.find(row.labelPos,'east') then table.insert(result,"text-align: left; ") linkoffset=w if shape.textNG ~="0px" then table.insert(result,"transform-origin: left; transform: rotate("..shape.textNG.."); ") end elseif row.labelPos=="left" or string.find(row.labelPos,'west') then if shape.textNG ~="0px" then table.insert(result,"transform-origin: right;") end linkoffset=-w table.insert(result,"text-align: right; transform: translateX(-100%) "..trf.."; ") else table.insert(result,"text-align: center; transform: translateX(-50%) "..trf.."; ") end table.insert(result,'font-weight: normal; line-height: '..shape.textLH..'; letter-spacing:'..shape.textSP..'; vertical-align: bottom;') end if string.find(shape.textAT or '','bold') then textItem='<b>'..textItem..'</b>' end if string.find(shape.textAT or '','italic') then textItem='<i>'..textItem..'</i>' end if shape.textOL=='0px' and shape.textBG~='transparent' and dotItem==0 then table.insert(result,'\"><span style=\"background-color: '..getColor(shape.textBG)..'; color:inherit;\">'..textItem..'</span></div>') else table.insert(result,"\">"..textItem.."</div>") end if tlink~='' and not string.match(row.param1 or "","nolink") then table.insert(result,makeLinkBox(row.gridx+row.dx+linkoffset, row.gridy+row.dy+bry, 16, row.dotTag..' '..row.title,tlink)) end end local function getshapetable(row,shape) -- Construct CSS divs for a dot from shape and map data local result={} local w,h,r=getsize(shape.shapeSize) local tshape,tlink=checkfortooltip(row.title,row.dx,row.dy,row.dotTag,row.dlink,string.match(row.param1 or "","nolink") ) local align=row.labelPos or '' local offsetx,offsety=0,0 local ty=tonumber(string.match((shape.tagSize or 9),"%d+" )) if row.labelText and row.labelText~='' then ty=tonumber(string.match((shape.textSZ or 11),"%d+" )) else align='center' end -- it is just for dotTag, so justify center -- identify align value and extend offsets local widthzone,heightzone = w/2+4,h/2+4 local theta,r = math.deg( math.atan2(row.dy, row.dx)) , math.sqrt(row.dx^2 + row.dy^2) if align=='auto' or align=='' then if (theta < -112 or theta > 112) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "left" offsetx=1 elseif (theta > -68 and theta < 68) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "right" offsetx=-1 elseif theta <0 then offsety=ty/2-1 -- bottom else offsety=0-ty/2+1 -- top end elseif align=='left' or string.find(align,'west') then row.dx=row.dx - w/2 widthzone=4 offsetx=-1 elseif align=='right' or string.find(align,'east') then row.dx=row.dx + w/2 widthzone=4 offsetx=1 offsety=-ty/2 +1 elseif align=='top' or align=='north' then if string.find(shape.shape,'curve') then row.dy=row.dy-(h/2)-12 heightzone=4 offsety=ty/2-1 else row.dy=row.dy - h-2 heightzone=4 offsety=ty/2-1 end elseif align=='bottom' or align=='south' then if string.find(shape.shape,'curve') then row.dy=row.dy+(h/2) heightzone=4 offsety=ty/2-1 else row.dy=row.dy + h+2 heightzone=4 offsety=0-ty/2-1 end end if r > widthzone and shape.textEW ~= "0px" and not string.match(row.param1 or "","noline") then makeLineTo(result, row.gridx+1, row.gridy-1, row.gridx+row.dx+offsetx, row.gridy+row.dy+offsety, shape.textEW, shape.textES,shape.textEC,1) end if row.posType and row.posType=='mark-line' then if row.gridx2 and row.gridy2 then makeLineTo(result, row.gridx, row.gridy, row.gridx2, row.gridy2, tostring(row.mlWidth)..'px', row.mlStyle, getColor(shape.textEC),row.mlGap) -- debugmsg('makeLineTo line drawn from '..row.code..' with width '..tostring(row.mlWidth)..'px '..row.mlStyle..' and color '..getColor(shape.textEC)) end end if w ~= 0 then if shape.shape=='itriangle' then shape.shape='triangle' shape.shapeSize=tostring(w)..'px'..tostring(w/2)..'px' end if shape.shape=="triangle" then if shape.outlineWidth ~= "0px" then makeTriangle(result,row,shape,true,false) -- larger triangle to give the outline, if required end makeTriangle(result,row,shape,false,tshape) -- smaller triangle to fit over the top elseif shape.shape=="square" or shape.shape=="box" or shape.shape=='panel' then makeSquare(result,row,shape,tshape) elseif shape.shape=="circle" or shape.shape=="ellipse" then makeCircle(result,row,shape,tshape) elseif string.find(shape.shape,'image:')==1 then makeImage(result,row,shape) elseif shape.shape=="rulea" or shape.shape=='rule' then makeRuleA(result,row,shape) elseif shape.shape=="curvea" or shape.shape=="curvec" then makeCurveA(result,row,shape) else -- use a pathshape clipPath if shape.outlineWidth ~= "0px" then makeClipPath(result,row,shape,true,false) -- larger path-shape to give the outline, if required end makeClipPath(result,row,shape,false,tshape) end end if row.ppwidth and row.ppwidth>0 then makePhotoPanel(result,row,shape) end if shape.shape=='panel' and row.labelText then makePanelText(result, row, shape) else if row.dotTag and row.dotTag ~= "" then -- there is a dotTag if (row.dx==0 and row.dy==0) and w>0 then -- it is on the dot so if dotsize is not 0 any label is ignored makeTextItem(result, row, shape, align, tlink, '<b>'..row.dotTag..'</b>', 1) else if row.labelText and row.labelText~='' then -- tag and label both used makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.labelText, 0) else makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.dotTag, 2) -- tag is ouside the dot end end else if (row.labelText and row.labelText~='') then -- just the label. No tag makeTextItem(result, row, shape, align, tlink, row.labelText, 0) end end end if tlink and tlink~='' then table.insert(result,makeLinkBox(row.gridx, row.gridy,w+3,row.dotTag..' '..row.title,tlink)) end return table.concat(result) end local function getmapframecontent(args,use) local result, comma= {}, '' local propertyTable= {} local color=splitItem(string.lower( args['map-data-color'] or 'dark orange'), 2) if use == 'basemap' then table.insert(result, '[') propertyTable.stroke = "#000000" propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') propertyTable['stroke-opacity'] = tonumber(color[2] or '15')/100 else propertyTable.title = args['map-data-text'] or args['map-data'] or '' propertyTable.stroke = getColor(color[1]) propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') propertyTable['stroke-opacity'] = tonumber(color[2] or '100')/100 end if args['map-data-inverse'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geomask', ids = args['map-data-inverse'], properties = {title = (args['map-data-text'] or ''), stroke = '#555555', fill = '#555555', ['fill-opacity'] = 0.1, ['stroke-width'] = 1, ['stroke-opacity'] = 0.5 } } table.insert(result, mapJson) comma=', ' end if args['map-data-heavy'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-heavy'], properties = { title = args['map-data-heavy'], stroke = propertyTable.stroke, ['stroke-width'] = 9, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.25 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data-light'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-light'], properties = { title = args['map-data-light'], stroke = propertyTable.stroke, ['stroke-width'] = 3, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.25 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data'] then propertyTable['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.1 local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data'], properties = propertyTable } table.insert(result, comma .. mapJson) comma=', ' end if args['map-wdqs'] then -- inserts a SPARQL wdqs query to find geopoints, geoshapes etc from wikidata local mapSparql = mw.text.jsonEncode{ type = 'ExternalData', service = args['map-wdqs-type'] or 'geopoint', query = args['map-wdqs'], } table.insert(result, comma .. mapSparql) -- debugmsg('mapSparql = ' .. mapSparql) end if args['map-raw'] then table.insert(result, comma .. args['map-raw']) end if use=='basemap' then table.insert(result,']') end return table.concat(result) end --eg | minilocator=filename,bottom right,132px153px, 38%,60%, 22px local function makeLocatorMap (args, result) local miniFile,pos,itemlist,miniW,miniH, miniX,miniY,miniBox, miniBH if args['mini-locator'] then pos,miniFile=extractItem(args['mini-locator']) -- first item is filename, in quotes if it includes commas itemlist=splitItem(pos,5) -- put items in a table filename removed, position,WpxHpx, x%y%,box pos=itemlist[2] or 'right' miniW,miniH=getsize(itemlist[3]) miniX=tonumber(string.match(itemlist[4] or '0','[%d]+'))*miniW/100 miniY=tonumber(string.match(itemlist[5] or '0','[%d]+'))*miniH/100 miniBox=tonumber(string.match(itemlist[6] or '0','[%d]+')) miniBox=miniBox*miniW/100 miniBH=miniBox * maplist.height/maplist.width elseif args['mini-file'] then miniFile = args['mini-file'] pos=string.lower(args.minimap or 'right') if pos=='file' then pos='right' end miniW,miniH = tonumber(args['mini-width'] or 60), tonumber(args['mini-height'] or 60) -- find top left corner of locator miniBox,miniBH=tonumber(args['minimap-boxwidth'] or '0'),0 -- firm up pos offsets for dot (with % or not) and boxsize if any, miniX, miniY=args['minipog-gx'],args['minipog-gy'] if not miniX then miniX=tonumber(args['minipog-x'] or '0') else miniX=tonumber(miniX)*tonumber(miniW)/100 end if not miniY then miniY=tonumber(args['minipog-y'] or '0') else miniY=tonumber(miniY)*tonumber(miniH)/100 end if args['minipog-gx'] then miniBox=miniBox*miniW/100 end miniBH=miniBox * maplist.height/maplist.width else return end local miniTop,miniLeft=0,1 if not string.find(pos,'top') then --only use top left, as link box is in top right or put bottom left or right miniTop=maplist.height+2-miniH if string.find(pos,'right') then miniTop=miniTop-15 -- to avoid (c) line miniLeft=maplist.width-1-miniW end end table.insert(result,'<div style="position: absolute; outline-width: 1px; outline-style: solid; outline-color: white;') table.insert(result,'top: '..tostring(miniTop)..'px; left:'..tostring(miniLeft)..'px; width:'..tostring(miniW)..'px;' ) table.insert(result,'background-color:transparent; color:inherit;">[[File:'..miniFile..'|'..tostring(miniW)..'px|File:'..miniFile..']]</div>') if miniX and miniX>0 then if args['minipog-y'] then miniY=miniY/1.04 end if args['minipog-x'] then miniX=miniX/1.04 end if miniBox<1 then table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-3-6)..'px; left:'..tostring(miniX+miniLeft-3)..'px;') table.insert(result, 'width: 6px; background-color:transparent; color:inherit;">[[File:Red pog.svg|6px|link=]]</div>') else table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-miniBH/2)..'px; left:'..tostring(miniX+miniLeft-miniBox/2)..'px;') table.insert(result, 'width: '..tostring(miniBox)..'px; height:'..tostring(miniBH)..'px; outline:1px solid #AA1205; background-color:#AA120522; color:inherit;"></div>') end end end local function makeArcText(args,result,nval) local items, itemlist='',{} local arcText='' if args['arc'..nval] then items=convertCoords (args['arc'..nval]) items,arcText=extractItem(items) -- first item is text, in quotes if it includes commas itemlist=splitItem(items,9) -- put items in a table: text, lat,lon,size,color,angle,gap,radius,ellipse if itemlist[4]=='' then itemlist[4]='12' end itemlist[4]=string.match((itemlist[4] or '12'),"[%.%-?%d]+") if itemlist[5]=='' then itemlist[5]='grey' end itemlist[6]=string.match((itemlist[6] or '0'),"[%.%-?%d]+") itemlist[7]=string.match((itemlist[7] or '0'),"[%.%-?%d]+") itemlist[8]=string.match((itemlist[8] or '0'),"[%.%-?%d]+") end if args['arc-coord'..nval] then local itemTab=splitItem(convertCoordsTrad (args['arc-coord'..nval]),2) maplist.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) maplist.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) else maplist.lat=tonumber(string.match(args['arc-lat'..nval] or itemlist[2] or '0',"[%.%-?%d]+")) maplist.lon=tonumber(string.match(args['arc-lon'..nval] or itemlist[3] or '0',"[%.%-?%d]+")) end local arcX,arcY=maptogrid(maplist,6) arcText = args['arc-text'..nval] or arcText local fontSize =tonumber(args['arc-text-size'..nval] or itemlist[4] or '12') local textColor=getColor(string.gsub(args['arc-text-color'..nval] or itemlist[5] or 'grey','[%s]+','') ) local arcAngle= tonumber((args['arc-angle'..nval]) or (itemlist[6]) or '45')-90 local arcRadius =tonumber(args['arc-radius'..nval] or itemlist[8] or '0.05') local arcGap = tonumber(args['arc-gap'..nval] or itemlist[7] or '1')* ( ( math.sin(8-math.rad(arcRadius))^8 )+0.4 )*( ( fontSize+6 )/15 ) arcRadius=450*arcRadius*0.75 local ellipseFactor=tonumber(args['ellipse-factor'..nval] or itemlist[9] or '1') local arcRotate =arcAngle+90 if arcGap<0 then arcRotate=arcAngle-90 end local latF=arcY - fontSize + (0-(math.sin(math.rad(arcAngle))) * arcRadius) local lonF=arcX - fontSize +(0-(math.cos(math.rad(arcAngle))) * arcRadius) local step=1 for codepoint in mw.ustring.gcodepoint( arcText ) do -- block step=1,#arcText do table.insert(result,'<div style="position: absolute;') local posY=tostring(round( (latF + (math.sin(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius)) *ellipseFactor,2))..'px;' local posX=tostring(round( (lonF + (math.cos(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius))/ellipseFactor,2))..'px;' table.insert(result,' top: '..posY..' left: '..posX..' transform: rotate( '..tostring(round(arcRotate +((step-1)*arcGap)),2)..'deg);') table.insert(result,'width:'..tostring(fontSize*2)..'px; text-align: center; background-color:transparent; color: '..textColor..';') table.insert(result,'vertical-align: baseline; font-size: '..tostring(fontSize)..'px;">'..mw.ustring.char(codepoint)..'</div>') step=step+1 end end local function makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) local item={} itemdescription=stripdivs(itemdescription or '') local templon=lon if lon > 180 then templon=lon-360 end --for hemisphere+ or -1 dots if lon < -180 then templon=lon+360 end -- use 'real' coordinates for geohack label, while retaining shifted coords for plot if itemcolor=='transparent' then itemcolor='white' end itemcolor=getColor(itemcolor) -- ensure no opacity, which breaks maplink if string.find(itemcolor,'#')==1 and #itemcolor>7 then itemcolor=string.sub(itemcolor,1,7) end table.insert(item, '{ "type": "Feature", "properties": {') table.insert(item, ' "title": "'..itemtitle..'",') table.insert(item, ' "description": "'..itemdescription) table.insert(item, ' ([https://geohack.toolforge.org/geohack.php?params='..tostring(lat)..';'..tostring(templon)) table.insert(item, '_dim:2000 '..tostring(lat)..','..tostring(templon)..'])",') table.insert(item, ' "marker-symbol": "-number-'..string.gsub(group,'%W','')..'", "marker-size": "medium", "marker-color": "'..itemcolor..'" },') table.insert(item, ' "geometry": {"type": "Point", "coordinates": ['..tostring(lon)..','..tostring(lat)..'] } }') return table.concat(item) end local function makeLegendBox(result,args) local legend ={} local line,count, maxWidth='',1,8 local item line,item=extractItem(args.legendBox or '') local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end legend.Text = a -- convert hats to line breaks line=splitItem(line,6) -- (text, size, poition,background color, text/outline color, param options) legend.Size=line[2] or '150px80px1px' legend.Pos=line[3] or '10px10px' legend.Background=line[4] or 'beigeground' legend.Color=line[5] or 'darkbrown' legend.Param= line[6] or '' local argnum,legendCount,titleHeight='1',1,0 if (legend.Text and legend.Text~='') then titleHeight=15+(13.4*(select(2, string.gsub(legend.Text,"<br>", "")))) end local legendLine,legendGroup,legendY={},{},{} while args['legendItem'..argnum] do -- assign legendLine, legendGroup, legendY for each dot line,legendLine[legendCount] = extractItem(args['legendItem'..argnum] or '') line=splitItem(line,3) legendGroup[legendCount]=line[2] or argnum if shapeList[legendGroup[legendCount]] then maxWidth=math.max(tonumber(string.match(shapeList[legendGroup[legendCount]].shapeSize or '10','[%d]+')),maxWidth) else maxWidth=math.max(tonumber(string.match(shapeList['1'].shapeSize or '10','[%d]+')),maxWidth) end maxWidth=maxWidth+1 if line[3] then legendY[legendCount]=tonumber(string.match(line[3],'[%d]+')) else legendY[legendCount] = 3+maxWidth*(legendCount-1)+titleHeight -- if (legend.Text and legend.Text~='') then legendY[legendCount]=legendY[legendCount]+15 end end legendCount=legendCount+1 argnum=tostring(legendCount) end local w,h,r=getsize(legend.Size or '') local x,y=getsize(legend.Pos or '') local div=mw.html.create ('div') div:css('position', 'absolute') div:css('outline', '1px solid'..getColor(legend.Color)) if r~=0 then div:css('border-radius',tostring(r).."px") end div :css('top', y.."px") :css('left', x.."px") :css('width', w.."px") :css('height', h.."px") :css('line-height','105%') :css('background-color', getColor(legend.Background) ) :css('color','inherit') if not string.find(legend.Param,'noshadow') then div:css('box-shadow', '2px 2px 4px #33203335') end div:tag( 'div' ) :css('position', 'absolute') :css('top','1px') :css('left', (w/2).."px") :css('width',(w-8)..'px') :css('text-align', 'center') :css('color', getColor(legend.Color)) :css('transform', 'translateX(-50%)') :css('font-size','11px') :wikitext(legend.Text) :done() for lct=1,legendCount-1 do --local t=legendGroup[lct] local shape=shapeList[legendGroup[lct]] or shapeList['1'] local row={} row.gridx=3+maxWidth/2 row.gridy=(legendY[lct] or 0) + 5 if shape.shape=='image:' then row.gridy=row.gridy+6 end row.dx=0 row.dy=0 local legendShape= getshapetable(row,shape) div:wikitext(legendShape) :tag( 'div' ) :css('position', 'absolute') :css('top',(legendY[lct] or 0)..'px') :css('left', (maxWidth+6)..'px') :css('width', (w-maxWidth-6)..'px') :css('text-align', 'left') :css('line-height','103%') :css('color', getColor(legend.Color)) :css('font-size','10px') :wikitext(legendLine[lct]) :done() end div:allDone() table.insert(result,tostring(div)) end function p._main ( args ) local result={} local frame=mw.getCurrentFrame() local dotTable={} local magVal,scaleVal = args.magnify,'' local origH,origW=maplist.height,maplist.width if magVal then --set up the values needed to magnify the top-right portion of the map magVal=tonumber(string.match(magVal or '0',"[%.%-?%d]+")) or 1 if magVal>1 and magVal <=2 then maplist.height= round(maplist.height/magVal,0) maplist.width=round(maplist.width/magVal,0) scaleVal='transform: scale('..magVal..') translateY('..tostring((origH-maplist.height)/2)..'px);' else magVal=1 end --debugmsg('magVal='..magVal..', Magnify: H='..maplist.height..', ('..origH..'), W='..maplist.width..', ('..origW..'), scale='..scaleVal) --debugmsg('top: '..tostring(((origW-maplist.width))/2)..'px;right: '..tostring(0-((origW-maplist.width))/2)..'px; (alt top: '..tostring(((origH-maplist.height))/2)..'px;)') end -- set up the three nested div boxes (plus an extra if centered) to put the map plus title/caption area, in an appropriate frame on the page if args.float=='center' or args.float=='centre' then table.insert(result,'<div class="center"><div class="thumb tnone">') elseif args.float=='left' then table.insert(result,'<div class="thumb tleft">') else table.insert(result,'<div class="thumb tright">') end table.insert(result,'<div class="thumbinner" style="position: relative; top: 0px; right: 0px; width: '..(args.width or "400")..'px;">') if args.title then table.insert(result,'<div class="center" style="font-weight:bold">'..args.title..'</div>') end if magVal and magVal >1 and magVal<=2 then table.insert(result,'<div class="thumbinner noresize" style="display:block; position: relative; outline:0px; border:0px; padding:0px; background-color:transparent; color:inherit;') table.insert(result,'top: 50%; right: '..tostring(0-((origW-maplist.width))/2)..'px; ') table.insert(result,'height: '..origH..'px; width: '..maplist.width..'px; '..scaleVal..'">') else magVal=1 table.insert(result,'<div class="thumbinner noresize" style="position: relative; outline:0px; border:0px; padding:0px;') table.insert(result,'top: 0px; right: 0px; ') table.insert(result,'height: '..maplist.height..'px; width: '..maplist.width..'px ">') end -- Create the basemap using mapframe local mapframecontent=getmapframecontent(args,'basemap') table.insert(result, frame:extensionTag{ name ='mapframe', content=mapframecontent, args={width=tostring(maplist.width), height=tostring(maplist.height), zoom=tostring(maplist.zoom), longitude=tostring(maplist.lonbase), latitude=tostring(maplist.latbase), mapstyle=maplist.mapstyle, frameless=true } } ) --Add coverall box to block the unhelpful links from mapframe - which wouldn't include all the dots. Reinstate some links to osm and wikimedia table.insert(result,'<div style="position: absolute;width:'..tostring(maplist.width)..'px; height:'..tostring(maplist.height)..'px;') table.insert(result,'top:0px;left:0px;background-color:#FFFFFF00; color:inherit;"></div>') --Add replacent hover-links for OpenStreetMap and maps terms and conditions table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-13)..'px; width: 12px; height: 12px">') table.insert(result,'[[file:Transparent.svg|12px|link=https://www.openstreetmap.org/copyright|'..aboutOSM..']]</div>') table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-110)..'px;') table.insert(result,'width: 12px; height: 12px background-color: transparent; color:inherit;">') table.insert(result,'[[file:Transparent.svg|12px| link=https://foundation.wikimedia.org/wiki/Policy:Maps_Terms_of_Use|'..termsOfUse..']]</div>') -- Add scale-line if not args.scalemark or args.scalemark~='0' then local top=maplist.height-42 local left=maplist.width-61-(tonumber(args.scalemark or '1')) local minipos=string.lower(args.minimap or '') -- scalemark gets pushed left if it would be behind the minimap if minipos~='' and not(string.find(minipos,'left') or string.find(minipos,'top') ) then local offset=tonumber(args.scalemark or '1')-tonumber(args['mini-width'] or '60') if offset<1 then left=maplist.width-61-tonumber(args['mini-width'] or '60') end end if maplist.width-left >216 then top=top+14 end -- shunt scaleline down if it is beyond the copyright stuff local scalek,scalem=getScale(maplist.zoom, maplist.latbase, magVal) local magReduce='' table.insert(result,"<div style=\"display:inline-block; position: absolute; background-color: #111111") table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;M0,8 l0,4 l20,0 l0,-4 l-0.3,0 l0,3.7 l-19.4,0 l0,-3.7 z&#39;) ") table.insert(result,"; width:20px") -- path is a 20px20px box, and then rescales if magVal==1 then table.insert(result,"; top:"..tostring(top-1).."px") table.insert(result,"; left:"..tostring(left+16).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5)..", "..tostring(1.5)..")") else -- shrink the scalemark to compensate for magnification table.insert(result,"; top:"..tostring(top-(magVal*0.28)).."px") table.insert(result,"; left:"..tostring(left+(16*(magVal*1.15))).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5*(1/magVal))..", "..tostring(1.5*(1/magVal))..")") magReduce= 'scale('..tostring(1/magVal)..')' end table.insert(result,"\"></div>") table.insert(result,'<div style="position: absolute; top: '..tostring(top)..'px; left: '..tostring(left+47)..'px; font-size: 9.5px; line-height: 126%; width: fit-content;') table.insert(result,'color: #444433; background-color: transparent; text-align: right; transform: '..magReduce..' translateX(-100%);">'..scalek..'<br>'..scalem..'</div>') end --Set up the shapeList and dotList tables, to provide data to go on the map local sgNumbers,sgSortable={},{} --s1,s2 sgNumbers["1"]="1" if args.useFormatStyle and args.useFormatStyle=='shortstyle' then shapeList=ParseShapeTypes (shapeList,args,"1") for argindex,argv in pairs(args) do -- build a list of all the numbered sg's that have been used if string.find(argindex,"sg[a-f,n%d]+") == 1 then -- only look through the sga,sgb, sgc,sgd,sge,sgf and sgn args local x=string.match(argindex,"[%d]+") -- find its number if x and not sgNumbers[x] then sgNumbers[x]=x end -- only add if not already found end if string.find(argindex,"sg[a-f]H") == 1 and highlightNum then highlightOption=true end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form for k,v in pairs(sgSortable) do -- work through the sorted list, parsing each set of sg's in turn, from 1 upwards shapeList=ParseShapeTypes (shapeList,args,v) end if highlightOption==true then shapeList=ParseShapeTypes (shapeList,args,'H') end end local dotList,dotresult,dotItemTable,dotGroupList={},{},{},{} if (not args.useFormatStyle) or args.useFormatStyle=='standardstyle' then local dotmax=0 for indx in pairs(args) do if string.match(indx,'mark%-coord[%d]+') or string.match(indx,'lat[%d]+') then dotmax=math.max(dotmax, tonumber(string.match(indx,"[%d]+"))) end if (indx=='shapeH') or (indx=='shape-colorH') or (indx=='shape-outlineH') or (indx=='label-colorH') then highlightOption=true end end dotItemTable=tradstyleParseShapes(args,dotItemTable,dotmax) for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) then sgNumbers[x]=x -- add it to the list end end else for indx in pairs(args) do if string.match(indx,'dot[%d]+') then table.insert(dotList,indx) end --add the index name for dot1, dot2 etc to dotList end table.sort(dotList,morethan) for indx,dotName in pairs(dotList) do dotresult=ParseData(args,string.match(dotName,'[%d]+') ) -- using each dot number, assign the settings for each dot to a dotresult item line table.insert(dotItemTable,dotresult) -- and store that item line within the dotItemTable end end for arcVal = 65,91 do -- check through args looking for any arcs local arcLetter=string.char(arcVal) if args['arc-text'..arcLetter] or args['arc'..arcLetter] then makeArcText(args,result,arcLetter) end end local dotdivs='' local ddots=0 if dotItemTable[1] then local ddots=(dotItemTable[1].lat or 0)+(dotItemTable[1].lon or 0) end local fgroup='F'..tostring(maplist.latbase+maplist.lonbase+ddots ) local FullscreenList={} local addcomma='' for i,dotitem in pairs(dotItemTable) do -- working throug each dot item, merge the dot and shape values into a full set of css text local dotgroup= dotitem.group or "0" if dotitem.posType=='mark-line' and dotitem.markDest then --find destination xy values for any mark-lines for n,v in pairs(dotItemTable) do if v.code == dotitem.markDest then dotitem.gridx2=v.gridx dotitem.gridy2=v.gridy break end end end local qtype=dotitem.group -- find which shape group each dot has been assigned --debugmsg('dotgroup='..qtype..', sg='..(sgNumbers[qtype] or 'nil')..' , shapeList='..shapeList[qtype].shape) if not sgNumbers[qtype] then qtype="0" end --shapeList[dotitem.group] will give access to the shape values for that dot if highlightNum==dotitem.code and highlightOption==true and shapeList['H'] then table.insert(result, getshapetable(dotitem,shapeList['H'])) else table.insert(result, getshapetable(dotitem,shapeList[qtype])) -- Add the actual css instructions for each dot end if shapeList[dotgroup] and not string.find((dotitem.param1 or ''),'nomap') then -- only add if not excluded with 'nomap' labelText local ftext='' if dotitem.dotTag~='' and not string.match(dotitem.labelText or '','[%d]') then ftext=stripdivs(dotitem.dotTag or '')..' <br>' end if (dotitem.labelText ~= ftext) and dotitem.dotLink =='' then ftext=ftext..' '..stripdivs(dotitem.labelText)..'<br>' end if (dotitem.dotLink) and (dotitem.dotLink ~='') then ftext=ftext..dotitem.dotLink..'<br>' end if dotitem.imageName then ftext=ftext..'[[File:'..dotitem.imageName..'|250px]]' end table.insert(FullscreenList,1, makeFullscreenItem (string.gsub(ftext,"[\n]+"," "), dotitem.info,dotitem.lat,dotitem.lon,fgroup,shapeList[dotgroup].shapeColor)..addcomma ) addcomma=', ' end -- makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) -- Always add to start of list, to reverse the sequence, and separate with commas except for first item, which is now at the end end if args.legendBox then makeLegendBox(result,args) end if args.minimap or args['mini-locator'] then makeLocatorMap(args,result) end -- add tag link and details for fullscreen version addcomma='' if (mapframecontent or '[]') ~= '[]' then addcomma=',' end mapframecontent=getmapframecontent(args,'fullscreen') local contentstart='[ '..mapframecontent..addcomma..'{ "type": "FeatureCollection", "features": [ ' --extra features after first square bracket local contentend=' ] } ]' table.insert(result, '<div style="position: absolute;top: 9px;left: '..tostring(maplist.width-34)..'px">') table.insert(result, '<div style="color: white; opacity:100; font-size: 19px; font-weight:normal; text-align: left;">') table.insert(result, frame:extensionTag{ name ='maplink', content=contentstart..table.concat(FullscreenList)..contentend, args={zoom=tostring(maplist.zoom+1), class='no-icon', frameless='1', latitude=tostring(maplist.latbase), longitude=tostring(maplist.lonbase), --add invisble 'en-spaces' for tooltip text='<div title="'..fullscreenlinktext..'">&nbsp;&nbsp;&nbsp;</div>'} } ) table.insert(result,'</div></div>') -- end of maplink ----- -- add closing div for main map table.insert(result,'</div>') -- collate caption material to go in the outer div class local autocaption=string.lower(args['auto-caption'] or 'no') local autoOff=autocaption:match("(%w+)(.*)") -- select the first word in autocaption and see if it is a negativeAnswer) if args.caption or not negativeAnswer[autoOff] then table.insert(result,'<div class="thumbcaption" style="text-align:left">') if args.caption then table.insert(result,args.caption) end end local columns=tonumber(autoOff:match("[%d]+") or '1') if columns>1 then columns=round(maplist.width/(columns*17), 0) end -- convert from em to px for historical reasons --for k in pairs(dotList) do capchk=capchk..(args["dotlink"..k] or '') end local capchk=nil local captionList = {} for key, value in pairs(dotItemTable) do -- only add an autocaption line if there is both a dotTag and a dotLink line available and it is not marked as nolist if value.dotTag and value.dotTag~='' and (not string.find(value.param1 or '','nolist')) and string.gsub(value.dotLink or '',"%s+","")~='' then table.insert(captionList, {key = key, value = value}) capchk=true end end if capchk and (not negativeAnswer[autoOff]) then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) local myDivision = string.gsub((args.toggletext or toggletext), "%s+", "") if string.find(autocaption,'collaps') then table.insert(result,'<hr><div class="mw-customtoggle-'..myDivision..'">'..(args.toggletext or toggletext)..'</div>') if string.find(autocaption,'collapsed') then table.insert(result,'<div class="mw-collapsible mw-collapsed" id="mw-customcollapsible-'..myDivision..'">') else table.insert(result,'<div class="mw-collapsible" id="mw-customcollapsible-'..myDivision..'">') end end if string.find(autocaption, 'columns=') then columns=string.match(autocaption,'[%d]+',string.find(autocaption, 'columns=') ) end table.insert(result,'<div class="mw-collapsible-content" style="column-count:'..columns..'; column-rule:solid 1px;text-align:left;padding-top:5px">') table.sort(dotList,lessthan) local nval,ngrp='','0' for k,v in pairs(captionList) do nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag ngrp=v.value.group or '0' if v.value.dotLink and v.value.dotLink~='' and nval and nval~='' then local c1,c2 if nval==args.highlight then c1,c2=checkColors(shapeList['H'].shapeColor) else c1,c2=checkColors(shapeList[ngrp].shapeColor) end table.insert(result,'<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;') table.insert(result,'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div> '..v.value.dotLink..'<br>') end end table.insert(result,'</div>') -- end for caption-content div if string.find(autocaption,'collaps') then table.insert(result,'</div>') end -- end for toggle frame end if args.caption or not negativeAnswer[autoOff] then table.insert(result,'</div>') end -- end for whole caption frame table.insert(result,'</div></div>') -- outer two frames if args.float == 'center' or args.float=='centre' then table.insert(result,'</div>') end if args['show-new-format'] == 'hints' then -- provide a 'format hint panel' in the 'Preview Box' local w="<small>Below are some template hints for the 'sga' compressed version, " w=w..'{{tl|OSM Location dots}}. It can use these, instead of the more verbose {{tl|OSM Location map}} parameter format. ' w=w..'Data is divided between a "ShapeGroup" and the "Dots", so that a single shapeGroup can be used for multiple dots on the map. ' previewMsg(w..'(nb. the "group" value can be the number or an assigned name of a shapeGroup)</small>') w='<small>{{tl|OSM Location dots}}: | dot(n)=group,lat,lon,dotTag | ' w=w..'dotlink=link/tooltip | dotlabel=label,position,dx,dy,param1,info | dotpic=filename <br>' w=w.."(nb. param1 options include 'nolink' 'nolist' 'nomap' 'hemisphere-1' 'hemisphere+1', 'noline' - quotes not required, separate with spaces).<br>" w=w..'| sga = Shape,Sizepx,Color,Angledeg | sgb= OutlineWidth,Color,Style | sgc=TextSize,Color,Angle,bold italic <br>' w=w..'| sgd=TagSizepx,Color,Spacer,Angledeg | sge=LineWidth,Color,Style | sgf=TextSpacingpx,LineHeight%,Outlinepx,backgroundColor<br>' w=w..'| sgn=Name (optional, to assign a meaningful name) | sgp=Parent (can be the name or number of the parent shapeGroup. ' previewMsg(w..'Each shapegroup will inherit values from a parent, stretching back to "sga1" and its default values.)</small>') if #pmsg then local dbg={} for i,x in pairs(pmsg) do table.insert(dbg,x..'<br>') end dbg=mw.addWarning(table.concat(dbg)) table.insert(result, dbg) end end if args.coordtest then debugmsg(mw.text.nowiki(args.coordtest)) end for i,x in pairs(msg) do table.insert(result, x..'<br>') end return table.concat(result) end function p.main(frame) local args = getArgs(frame) local itemTab={} maplist.width=tonumber(args.width) or 400 maplist.height=tonumber(args.height) or 300 if args.coord then itemTab=splitItem(convertCoordsTrad (args.coord),2) maplist.latbase=itemTab[1] maplist.lonbase=itemTab[2] else maplist.lonbase=tonumber(args.lon) or 5 maplist.latbase=tonumber(args.lat) or 0 end maplist.zoom=tonumber(args.zoom) or 1 visibleLinks=args.showlinks highlightNum=args.highlight if args.nolabels=='1' then maplist.mapstyle='osm' else maplist.mapstyle='osm-intl' end return p._main(args) end return p qvw2xergyo0d7mk9g1obxfaf679472g 204461 204460 2026-07-05T15:06:19Z HaxelKaszëba 17492 204461 Scribunto text/plain require('strict') local delink=require('Module:Delink').delink local getArgs = require('Module:Arguments').getArgs local p = {} local maplist={} local sgNames={} local highlightOption=false local highlightNum local visibleLinks -- This module creates framed maps of anywhere in the world, at the required scale, and enables annotations, -- dots, shapes, lines and other ways to customise the area of the map being shown. It also provides a link -- to an interactive fullscreen version, which has locator dots instead of annotations and shapes. -- This is the 2025 successor module to a wiki-markup template version of 2024, which itself was a successor -- to the 'Graph'/VEGA driven template that was begun in 2016, until the Vega version was switched off in 2023. -- This module is called from template {{OSM Location map}}, which uses the same parameter formats as before. -- In addition it will be possible to use a more concise parameter format using the template {{OSM Location dots}} -- In general the css output from the two formats will be identical, but the the concise version will allow bits of -- greater control over some of the settings. -- see the documentation on the two template pages for details of how to use the mapping features. -- If language customisation is needed, there are text items below that can be translated. Also see the color table -- below with details of how to add additional color names to allow localised alternatives. -- (Translating other language shape-types could be possible, but has not currently been contemplated. -- Parameter name translation would be harder but likely to be possible, ideally still retaining compatibility -- with template calls already written using English). local negativeAnswer={no=1,'0'-1,off=1} local fullscreenlinktext='Click for interactive fullscreen map with links to nearby articles' local toggletext='[Hide/show caption list]' local termsOfUse='Maps: terms of use' local aboutOSM='About OpenStreetMaps' local shapeList={} --This sets up the 'factoryDefault' shape group 0 shapeList["0"]={shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border of the shape outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items text settings for labels textSZ="11px", textCL="darkgrey", textNG="0deg", --sgf further text settings textSP="0px", textLH="120%", textOL="0px", textBG="transparent", --sgd items for dotTag text settings tagSize="10px", tagColor="white", tagSpacer="0px", tagAngle="0deg", --sge items for extension line to connect label to dot textEW="0px", textEC="darkgrey", textES="solid" } local colorList={} -- used by colorLookup to catch unsupported colors (eg 'LimeGreen'), to convert to generic version colorList['green']='hardgreen' -- it could also be added to to include alternative language equivelants, for a quick solution. colorList['red']='hardred' -- colorList ['source'] = target colorList['white']='white' -- converts any color that includes 'source' into its equivelent target colorList['blue']='hardblue' -- note, for translation you can add to this list, rather than replace it, colorList['brown']='brown' -- which would mean existing map definitions in english would also still work, alongside translated ones colorList['grey']='hardgrey' colorList['gray']='hardgrey' colorList['purple']='hardpurple' colorList['orange']='hardorange' colorList['leaf']='hardleaf' --for a more thorough translation, you can add all the variants of the colors as further CTB elements and hex values or redirects local CTB={} -- set up a table of color names (the CTB Color table index) and html hash colorhex values. CTB["paleblue"],CTB["softblue"],CTB["hardblue"],CTB["darkblue"]="#D6E1EC","#77A1CB","#4B77D6","#1c559e" CTB["palered"],CTB["softred"],CTB["hardred"],CTB["darkred"] = "#FCC6C0","#EC644B","#DB3123","#AA1205" CTB["palegreen"],CTB["softgreen"],CTB["hardgreen"],CTB["darkgreen"]= "#D2F0E5","#81AF81","#269F46","#0b7527" CTB["paleleaf"],CTB["softleaf"],CTB["hardleaf"],CTB["darkleaf"]= "#dff5c1","#b5e376","#8cc244","#679c21" CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"]= "#E8E8D6","#AAAA88","#777755","#444433" CTB["palegray"],CTB["softgray"],CTB["hardgray"],CTB["darkgray"]=CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"] CTB["palebrown"],CTB["softbrown"],CTB["hardbrown"],CTB["darkbrown"]="#FAF6ED","#CCB56C","#AD7F14","#754910" CTB["palepurple"],CTB["softpurple"],CTB["hardpurple"],CTB["darkpurple"]="#e0d1e6","#c784e0","#a029cf","#7a05a8" CTB["paleorange"],CTB["softorange"],CTB["hardorange"],CTB["darkorange"]="#ffedc2","#ffcf61","#EEB533","#e39f05" CTB["black"],CTB["white"],CTB["yellow"]="#000000","#FFFFFF","#FAF039" CTB["background"],CTB["paleground"],CTB["beigeground"]="#f9f6f2","#FEFEFA","#F5F5DC" CTB["beige"]=CTB["beigeground"] CTB["aqua"],CTB["teal"],CTB["fuchsia"] = "#00FFFF","#008080","#FF00FF" CTB["maroon"],CTB["olive"],CTB["navy"] = "#800000","#808000","#000080" CTB["lime"],CTB["limegreen"],CTB["aquamarine"] = "#00FF00","#32CD32","#7FFFD4" CTB["silver"],CTB["yellow"],CTB["orchid"] = "#800000","#FFFF00","#DA70D6" -- set up a table of predefined clip-paths local pathshape={} pathshape.squaredd = "M 19,1.25 l 0,18 -18,0 0,-18 18,0m-1,1 -16,0 0,16 16,0 0,-16m-1,1 0,14 -14,0 0,-14 14,0zm-1,1 -12,0 0,12 12,0 0,-12zm-1,1 0,10 -10,0 0,-10 10,0z" pathshape.squared = "M 18,2.5 l 0,15 -15,0 0,-15 15,0m-1,1 -13,0 0,13 13,0 0,-13zm-1,1 0,11 -11,0 0,-11 11,0z" pathshape.triangledd="M 0 20,20 20,10 0,0 20ZM1.5 19,10 1.7,18.5 19,1.5 19ZM3 18,17 18,10 3.8,3 18ZM4.5 17,10 5.4,15.4 17, 4.5,17ZM6 16,13.8 16,10 7.4z" pathshape.triangled ="M1,18 l 18,0 l -9,-18 l -9,18zm1.7,-1.1 l 7.3,-14.6 l 7.3,14.6 l -14.6, 0zm1.7,-1 l 11.0,0 l -5.5,-11 l -5.5,11z" pathshape.circledd = "M0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm0.8,0a9.2,9.2 0 1,1 18.4,0a9.2,9.2 0 1,1 -18.4,0m1,0a8.2,8.2 0 1,0 16.4,0a8.2,8.2 0 1,0 -16.4,0zm0.8,0a7.2,7.2 0 1,1 14.8,0a7.2,7.2 0 1,1 -14.8,0m1,0 a6.4,6.4 0 1,1 12.8,0a6.4,6.4 0 1,1 -12.8,0z" pathshape.circled = "M2.5,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0m0.8,0 a5,5 0 1,1 11.4,0a5,5 0 1,1 -11.4,0" pathshape.diamond = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10z" pathshape.diamondd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm1,0 l 6,8.5 l 6,-8.5 -6,-8.5 -6,8.5zm1,0 l 5,-7 5,7 -5,7 -5,-7z" pathshape.diamonddd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm0.75,0 l 6.25,9 l 6.25,-9 -6.25,-9 -6.25,9zm0.75,0 l 5.5,-8 5.5,8 -5.5,8 -5.5,-8zm0.75,0 l 4.75,7 l 4.75,-7 -4.75,-7 -4.75,7zm0.75,0 l 4,-6 4,6 -4,6 -4,-6z" pathshape.crossd = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0zM2.3,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.cross = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0z" pathshape.thincross = "M2,12 l6,0 l0,6 l4,0 l0,-6 l6,0 l0,-4 l-6,0 l0,-6 l-4,0 l0,6 l-6,0z" pathshape.fivepointstar = "M10 0 L12.245 6.91 19.511 6.91 13.633 11.18 15.878 18.09 10 13.82 4.122 18.09 6.367 11.18 0.489 6.91 7.755 6.91Z" pathshape.fivepointstard = "M10 1.5 L 11.90825 7.3735 18.08435 7.3735 13.08805 11.003 14.9963 16.8765 10 13.247 5.0037 16.8765 6.91195 11.003 1.91565 7.3735 8.09175 7.3735 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sixpointstar = "M10 0 L12.323 5.977 18.66 5 14.645 10 18.66 15 12.323 14.023 10 20 7.677 14.023 1.34 15 5.355 10 1.34 5 7.677 5.977Z" pathshape.sixpointstard = "M10 1.5 L 11.97455 6.58045 17.361 5.75 13.94825 10 17.361 14.25 11.97455 13.41955 10 18.5 8.02545 13.41955 2.639 14.25 6.05175 10 2.639 5.75 8.02545 6.58045 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sevenpointstar = "M10 0 L12.048 5.747 17.818 3.765 14.602 8.95 19.749 12.225 13.69 12.943 14.339 19.01 10 14.72 5.661 19.01 6.31 12.943 0.251 12.225 5.398 8.95 2.182 3.765 7.952 5.747Z" pathshape.sevenpointstard = "M10 1.5 L11.7408 6.38495 16.6453 4.70025 13.9117 9.1075 18.28665 11.89125 13.1365 12.50155 13.68815 17.6585 10 14.012 6.31185 17.6585 6.8635 12.50155 1.71335 11.89125 6.0883 9.1075 3.3547 4.70025 8.2592 6.38495 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.eightpointstar = "M10 0 L11.88 5.46 17.071 2.929 14.54 8.12 20 10 14.54 11.88 17.071 17.071 11.88 14.54 10 20 8.12 14.54 2.929 17.071 5.46 11.88 0 10 5.46 8.12 2.929 2.929 8.12 5.46Z" pathshape.eightpointstard = "M10 0 L10 1.5 L11.598 6.141 16.01035 3.98965 13.859 8.402 18.5 10 13.859 11.598 16.01035 16.01035 11.598 13.859 10 18.5 8.402 13.859 3.98965 16.01035 6.141 11.598 1.5 10 6.141 8.402 3.98965 3.98965 8.402 6.141ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.ring="M2.6,9.5a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.boxd=pathshape.squared pathshape.boxdd=pathshape.squaredd pathshape.ellipsed=pathshape.circled pathshape.ellipsedd=pathshape.circledd local msg={} local function debugmsg(txt) table.insert(msg,txt) end local pmsg={} local function previewMsg(txt) table.insert(pmsg,txt) end local function colorLookup(color) for c,l in pairs(colorList) do if string.find(color,c) then return l end end return color end local function getColor (color,chk) local c local opacity="100" if not color or color=='' then color='hardgrey' end if color=="transparent" then return color end if color=="background1" then color='background' end if string.byte(color,1,1)==35 and (#color == 7 or #color == 9) then c=color elseif string.byte(color,1,1)==35 and #color == 4 then c=string.sub(color,1,2)..'f'..string.sub(color,3,3)..'f'..string.sub(color,4,4)..'f' else local s=color..'1' s= s:sub(0,s:find("%d")-1) opacity=string.match(color,"%d+") if not CTB[s] then s= colorList[s] end -- check for synonyms and translations if not CTB[s] then debugmsg(mw.addWarning('color = '..color..'. The color name is not defined. Used default grey instead')) end c=CTB[s] or CTB.hardgrey end if opacity and (tonumber(opacity) < 100) and string.find(c,"#")==1 and string.len(c)==7 and opacity~="" then local hexval=string.format("%x",(math.floor(tonumber(opacity)*2.55))) c=c..hexval end return c end function p.colorvalue(frame) -- enable external access to the CTB colorTable values. usage: {{#invoke:OSM Location map|colorvalue|color=hard blue}} local c if not frame.args.color or frame.args.color=='' then c='grey' else c=string.lower(string.gsub(frame.args.color,'%s+','')) end return string.upper(string.sub(getColor(c),2)) end local function checkColors(color) local c=getColor(color,'check') local opacity =1 -- calculate colour brightness and return black or white for contrast if c=='transparent' then return c,'#000000',0 end if not (string.find(c,'#')==1) then return c,'#FFFFFF',0 end if #c>8 then opacity= tonumber('0x'..(string.sub(c,8,9)))/500 end local r=tonumber('0x'..(string.sub(c,2,3)))/255 local g=tonumber('0x'..(string.sub(c,4,5)))/255 local b=tonumber('0x'..(string.sub(c,6,7)))/255 if 0.2126 * r + 0.7152 * g + 0.0722 * b / opacity < 0.7 then return c,'#FFFFFF',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity else return c,'#000000',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity end end local function morethan(a,b) a = tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]') or '0') b = tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]') or '0') return a>b end local function lessthan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) b = tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]') or '0') end return a<b end local function getsize(size) --size1 is between 1 and 3 values, each with px, equating to width,height,corner-rounding --eg '15px 25px 5px' (spaces are optional) or '18px'. returns three numbers local sizeval = {} for v in string.gmatch(size, "[^px]+") do table.insert(sizeval,v) end sizeval[1] = tonumber(sizeval[1]) or 13 sizeval[2] = tonumber(sizeval[2]) or sizeval[1] sizeval[3] = tonumber(sizeval[3]) or 0 return sizeval[1],sizeval[2],sizeval[3] end local function coord2text(coord) -- looks through the output from {{coord}} to find the lat and long decimal values -- and converts compass points to minus or not-minus, return with separating comma. local lat = string.match(coord,'[%.%d]+°[NS]') local lon = string.match(coord,'[%.%d]+°[EW]') local neg={N="",S="-",W="-",E=""} return neg[string.match(lat, '[NS]')]..string.match(lat,'[%.%d]+')..","..neg[string.match(lon, '[EW]')]..string.match(lon,'[%.%d]+') end local function convertCoordsTrad (row) local coords='' if row and string.find (row,'<span class="geo">') then local a,b=string.find(row,'<span class="geo">') local start=b+1 a,b=string.find(row,"</span>",b) local finish=a-1 coords=string.sub(row,start,finish) coords=string.gsub(coords,'; ',',') end return coords end local function convertCoords (row) local start,finish,lat,lon,coords,says if row then local a,b=string.find(row,"<span class=") start=a while a do -- find the final span> finish=b a,b=string.find(row,"span>",b) end if start then coords= string.sub(row,start,finish) says="" if string.find(coords,'<span class="error">') then error("coord error: badly formed coordinates",0) end coords=coord2text(coords) coords = string.sub(row,1,start-1)..coords..string.sub(row,finish+1) else coords=row end return coords else return "Nothing to see here" end end local function fillCommas(val,max) local line='' if not val then line=',' -- ensure there is some content else line = val --string.lower(string.gsub(val,"%s+","")) -- or strip spaces end if string.find(line,',') == 1 then line=' '..line end -- ensure initial comma is not skipped local _, count=string.gsub(line,",","") -- add enough subsequent commas for all entries line=line..string.rep(',',max-count) while(string.find(line,",,") ) do line=string.gsub(line,",,",", ,") --ensure string.gmatch doesn't ignore any empty items by padding with spaces end return line end local function makeLinkBox(left,top,wid,label, link) local linkBoxName='Transparent square.svg' if visibleLinks or '' =='yes' then linkBoxName='Red hollow square.svg' end local builder = mw.html.create('div') --display:inline-block; builder :cssText('position:absolute;left:'..tostring(left-1-wid/2)..'px;top:'..tostring(top-1 + math.min(wid/2-12,0) - wid/2)..'px') :wikitext(string.format( '[[File:%s|%dpx|link=%s|%s]]', linkBoxName, wid+2, link, label )) return tostring(builder) end local function extractItem(row,searchItem) -- remove text following a searchItem or start of line, which might be in quote-marks to allow commas local xend,xstart=1,0 if not row then return '','' end if searchItem then xend,xstart= string.find(row or '',searchItem or 'image:') end if not xstart then return string.gsub(string.gsub(row or '',"%b\"\"", ''),"%b\'\'", '') or '','' end while row:byte(xstart+1) == 32 and xstart<#row do -- skip over any leading spaces xstart=xstart+1 end local xbyte=row:byte(xstart+1) if xbyte == 34 or xbyte == 39 then -- are they wrapped in single or double quotes xstart=xstart+1 xend=row:find(string.char(xbyte),xstart+1) else xend = row:find(',',xstart+1) -- if no quotes, we assume no commas if not xend then xend=#row+1 end end -- return residual row and extracted text return row:sub(0,xstart)..row:sub(xend), row:sub(xstart+1,xend-1) end local function itemCheck(item,ext) if not item then return nil end if not ext then ext='' end return (string.match(item,"[%.%-?%d]+") or '0')..ext end local function stripdivs(line) return string.gsub(line or '',"%b<>", ' ') end local function splitItem(item,max) -- takes a commas-sep list and returns a table of lowercase items with no spaces, or nil local r={} local x=1 item=string.lower(fillCommas(item,max)) for t in string.gmatch(item,"[^,]+") do r[x]=string.gsub(t,"%s+","") if r[x]=='' then r[x]= nil else -- residual items might have commas if x>max then r[max]=(r[max] or '')..', '..r[x] end end x=x+1 end return r end local function ParseShapeTypes (result,args,sgval) -- for use with compressed, comma-separated 'sg plus dots' parameters --[[ shape table items and default values as set at top of page shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items label text textSZ="11px", textCL="white", textNG="0deg", textAT=attributes ("bold" and/or "italic") --sgd items for dotTag tagSize="11px", tagColor="darkgrey", tagSpacer="0px", tagAngle="0deg", --sge extension line textEW="1px", textEC="darkblue", textES="solid" --sgf fx for text labels textSP="0px" textLH="100%" textOL="1px", textBG="paleground", <!--| sga = shape,Size,Color,Angle|sgb= outlineWidth,color,style eg: sga1=circle,14px,blue,0deg| sgb1=0px,dark grey,solid | sgc=textSize,color,angle,bold/italic | sgd=tagSize,Color,Spacer,Angle eg: sgc1=11px,dark grey,0deg,normal| sgd1=9px,white,0px,0deg | sge=lineWidth,color,style |sgf=textspacing,lineHeight%,outlinepx,backgroundcolor, [bold,italic] eg: sge1=0px,black, solid| sgf1=0px,120%,0px,background | dot=shape-group/lat/lon/title/dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> --]] if args["sgn"..sgval] then local sgname=string.match(args["sgn"..sgval],"(%w+)(.*)") sgNames[sgname]=sgval end local parent= args["sgp"..sgval] if parent then parent=string.match(parent,"(%w+)(.*)") local pos= string.find(parent,"%d+") if pos == 1 then parent=string.match(parent,"%d+") else parent=sgNames[parent] or '1' end end if sgval~='H' then if not parent or tonumber(parent) > tonumber(sgval) then if sgval=="1" then parent="0" else parent="1" end end end local itemTab, line, filename result[sgval]={} result[sgval].shapeType=sgval line,filename=extractItem(args['sga'..sgval] or '','image:') if sgval=='1' and not args.sga1 then line='circle,12px,blue,0deg' end -- ensure there is a parent=1 sga result[sgval].shapeFile=filename or '' -- sga= Attributes for shape itemTab=splitItem(line,4) result[sgval].shape = itemTab[1] or result[parent].shape result[sgval].shapeSize=itemTab[2] or result[parent].shapeSize result[sgval].shapeColor=itemTab[3] or result[parent].shapeColor result[sgval].shapeAngle=itemCheck(itemTab[4],'deg') or result[parent].shapeAngle -- sgb= Border outline attributes for shape itemTab=splitItem(args['sgb'..sgval],3) result[sgval].outlineWidth=itemCheck(itemTab[1],'px') or result[parent].outlineWidth result[sgval].outlineColor=itemTab[2] or result[parent].outlineColor result[sgval].outlineStyle=itemTab[3] or result[parent].outlineStyle --sgc=character attributes for label itemTab=splitItem(args['sgc'..sgval],4) result[sgval].textSZ=itemCheck(itemTab[1],'px') or result[parent].textSZ -- size of text in px result[sgval].textCL=itemTab[2] or result[parent].textCL -- colour for text result[sgval].textNG=itemCheck(itemTab[3],'deg') or result[parent].textNG -- Angle for text result[sgval].textAT=itemTab[4] or result[parent].textAT -- attributes bold, and/or italic --sgd=dotTag attributes itemTab=splitItem(args['sgd'..sgval],4) result[sgval].tagSize=itemCheck(itemTab[1],'px') or result[parent].tagSize result[sgval].tagColor=itemTab[2] or result[parent].tagColor result[sgval].tagSpacer=itemCheck(itemTab[3],'px') or result[parent].tagSpacer result[sgval].tagAngle=itemCheck(itemTab[4],'deg') or result[parent].tagAngle --sge= extension line attributes itemTab=splitItem(args['sge'..sgval],4) result[sgval].textEW=itemCheck(itemTab[1],'px') or result[parent].textEW -- width result[sgval].textEC=itemTab[2] or result[parent].textEC -- colour result[sgval].textES=itemTab[3] or result[parent].textES -- style --sgf= fx for label text itemTab=splitItem(args['sgf'..sgval],4) result[sgval].textSP=itemCheck(itemTab[1],'px') or result[parent].textSP -- spacing value for letters result[sgval].textLH=itemCheck(itemTab[2],'%') or result[parent].textLH -- Angle for text result[sgval].textOL=itemCheck(itemTab[3],'px') or result[parent].textOL -- width of text-border line result[sgval].textBG=itemTab[4] or result[parent].textBG -- color for text background return result end local function round(x,dec) -- x=number [, dec=integer] returns numeric value with upto dec decimals (all but first trailing zeros get truncated) if (dec or 0)==0 then return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5) --this avoids .0 where dec=0 end dec =10^(dec) return x>=0 and math.floor(x*dec+0.5)/dec or math.ceil(x*dec-0.5)/dec end local function maptogrid(t,r) --[[ converts mercator projection longitude and latitude coordinates to x and y pixel coordinates, within a frame of given size, centre coordinates and zoom level. t is a table of named indices: {lon, lat, lonbase, latbase, width, height, zoom} output is two values, x and y, rounded to r decimal places--]] local x,y x=t.width/2 + ( ((math.rad(t.lon)*6378137) - (math.rad(t.lonbase)* 6378137) ) / (156543.03*math.cos(t.latbase/180)/(2^t.zoom) ) )*(1-(0.075*(math.abs(t.latbase)/90))) y=t.height/2 + ( ( (math.log(math.tan(math.rad(t.latbase)/2+math.pi/4))*6378137) - (math.log(math.tan(math.rad(t.lat)/2+math.pi/4))*6378137) ) / (156543.03*math.cos(t.latbase/180) / (2^t.zoom) ) )*(1-(0.075*(math.abs(t.latbase)/90))) return round(x,r),round(y,r) --source: python code at https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames and https://wiki.openstreetmap.org/wiki/Mercator --[[ width and height are the size, in pixels, of the map, which will be centerd around lonbase,latbase. Method: Convert lon and lonbase to meter-offsets from coord(0,0), and subtract lonbase from lon, zoom and latbase are used to scale the resulting meter-offset to pixels, and add it to width/2. Convert lat and lat-base to meter-offsets from coord(0,0), subtract lat from latbase, scale the resulting meter-offset to pixels, add it to height/2. A correction factor '*(1-(0.075*(math.abs(t.latbase)/90) ) )' compensates for an error that seems to creep in towards the edges of the map at higher latitudes. It was identified experimentally, and ensures a dot at the edge is in the same place as if that location is positioned at the centre. Original Python code for lat,lon to x,y where 0,0 is the centre of the map print('x=',width+(((math.radians(lon1) * 6378137)- (math.radians(lonbase) * 6378137))/ (156543.03*math.cos(latbase/180)/(2**zoom))),' y=',height+(( (math.log(math.tan(math.pi / 4 + math.radians(latbase) / 2)) * 6378137)- (math.log(math.tan(math.pi / 4 + math.radians(lat1) / 2)) * 6378137))/ (156543.03*math.cos(latbase/180)/(2**zoom))) ) --]] end local function getScale(zoom, lat, magVal) zoom = tonumber(zoom) or 0 lat = tonumber(lat) if not lat then lat = 0 -- fallback end if magVal then magVal = tonumber(magVal) if magVal > 1 and magVal <= 2 then zoom = zoom + (magVal - 1) end end local dist = (156543.03 * math.cos(math.rad(math.abs(lat))) / (2 ^ zoom)) / 17 local y if dist < 1 then y = round(dist * 10, 1) return tostring(y * 100) .. 'm', tostring(round(y * 109, 0)) .. 'yds' elseif dist < 18 then y = round(dist, 0) return tostring(y) .. 'km', tostring(round(y * 0.621371, 1)) .. 'miles' elseif dist < 500 then y = round(dist / 10) return tostring(y * 10) .. 'km', tostring(round(y * 6.21371, 0)) .. 'miles' else y = round(dist / 100) return tostring(y * 100) .. 'km', tostring(round(y * 62.1371, 0)) .. 'miles' end end local function ParseData (args,dotval) -- for use with compressed, comma-separated 'sg plus dots' parameters -- takes a structured series of comma-separated items which get parsed as the following: -- dot(n)= (sgNumber or Name),{{coord}} or (lat,lon), (dotTag) -- dotlink(n) = single-parameter text to give wikilink and/or title used by tootlip, fullscreen dots and autocaption list -- dotlabel(n) = 'label text',pos(left,roght,top,bottom,centre,auto),(dx), (dy) pixel offsets, params, info -- dotpic(n) = single parameter wikimedia filename for an image to use in photopanel and/or fullscreen dots -- dotfeature(n)= 'mark-line' (,linewidth,style,gap) or 'photo-panel' (,image-dim,panel-width,panel-height), draws line to n-1 -- label is used if either a position and/or an x,y offset are not 0,0 ( if no label then dotTag will be put at at the x,y offset, or over the dot -- label text can be autoaligned if x,y puts it left or right of the dot, or centered if above/below) -- quote marks are not needed unless including commas within the label text -- param1 is optional items separated by spaces, and can include [nolabel nolist nomap hemisphere+1 hemisphere-1] -- info is free wikitext, to be used in the fullscreen dot box. (use dotpic to show a picture) --<!--| dotx=shape-group,[lat,lon or {{coord}} ], dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> local result={} local count=1 local row = convertCoords (args["dot"..dotval]) -- swap in any {{coord}} values so they are csv lat and lon row=fillCommas(row,4) result.code=dotval -- store the parameter name as the id code for item in string.gmatch(row,"[^,]+") do -- iterate through 'row', adding each csv item in turn, if present if count==1 then --see if it is a number or a name local pos= string.find(item,"%d+") if pos == 1 then result.group=string.lower(string.gsub(item,"%s+","")) else item=string.match(item,"(%w+)(.*)") -- ensure just a single word result.group=sgNames[item] end elseif count==2 then result.lat=tonumber(string.match(item,"[%.%-?%d]+")) or 0-- find the number, with no non-numeric stuff elseif count==3 then result.lon=tonumber(string.match(item,"[%.%-?%d]+")) or 0 elseif count==4 then result.dotTag=item:match( "^%s*(.-)%s*$" ) or "" -- dotTag allows for internal spaces, but no commas end count=count+1 end row, result.labelText= extractItem(args["dotlabel"..dotval]) result.labelText= string.gsub(result.labelText,"[%^]+","<br>") -- convert hats to line breaks local item=splitItem(row,6) result.labelPos=item[2] or 'center' result.dx=tonumber(string.match(item[3] or '0',"[%.%-?%d]+")) or 0 result.dy=tonumber(string.match(item[4] or '0',"[%.%-?%d]+")) or 0 result.param1=string.lower(item[5] or '') if string.find(result.param1,"hemisphere-1",1,true) then result.lon=result.lon-360 elseif string.find(result.param1,"hemisphere+1",1,true) then result.lon=result.lon+360 end local txt = '' if item[6] then -- ensure all info elements are included, including commas count=1 local max=6 for t1 in string.gmatch(fillCommas(row,max),"[^,]+") do if count>max then txt=txt..',' end if count>=max then txt=txt..t1 end count=count+1 end end result.info=txt result.imageName = args['dotpic'..dotval] -- Get first wikilinked item (if any) from the args.dotlink and set this plus the delinked text local testx=args["dotlink"..dotval] or '' result.dotLink=testx if testx ~= '' then testx=stripdivs(testx) result.title=delink({ testx }) local linkstart= string.find(testx,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then result.dlink=delink( { string.sub(testx,linkstart,string.find(testx,']]',1,true)+1),wikilinks='target' } ) else result.dlink='' end else result.dlink='' result.title='' end if args['dotfeature'..dotval] then local item=splitItem(args['dotfeature'..dotval],6) if (item[1] or '') =='photo-panel' then result.ppwidth= tonumber(string.match((item[3] or '110'),"%d+")) -- panel width result.ppheight= tonumber(string.match((item[4] or '48'),"%d+") ) --panel height result.photowidth=round(tonumber(string.match((item[2] or '1.3'),"[%.%-?%d]+")) * result.ppheight+1,0) -- dimension to set image size result.photoImage=result.imageName result.posType='photo-panel' elseif (item[1] or '') == 'mark-line' then local x=tonumber(dotval or '0') result.markDest=item[5] or tostring(x-1) result.mlWidth= tonumber(string.match((item[2]) or '',"%d+") or '1') result.mlStyle= item[3] or 'solid' result.mlGap=tonumber(string.match((item[4] or ''),"[%d]+") or '0') result.posType='mark-line' -- debugmsg('making line for '..tostring(x)..'to '..tostring(result.markDest)..' with width '..tostring(result.mlWidth)) end --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end maplist.lon=result.lon maplist.lat=result.lat result.gridx, result.gridy = maptogrid(maplist,1) -- convert geo coords to grid xy - using values from maplist table return result end local function multiCheck (args, argName, argVal, defVal, alt) if not alt then alt='nonexistant' end if argVal=='H' and not args[argName..'H'] then argVal=highlightNum or '1' end if argVal=='' then return (args[argName] or args[alt] or (args[argName..'D']) or args[alt..'D'] or defVal) -- unnumbered args do not inherit from D or 1 else return (args[argName..argVal]) or (args[alt..argVal]) or (args[argName..'D']) or (args[alt..'D']) or (args[argName..'1']) or (args[alt..'1']) or defVal end end local function assignTradstyleShape(shapeResult,default,dotResult,args,nval) local item,itemTab local autoDotTag='' local shapeWidth,shapeHeight=0,0 local argval=nval -- to catch the unnumbered shape series if argval=='0' then argval='' end if nval=='H' then shapeResult.H={} end item=string.lower(multiCheck(args,'shape',argval,'image')) if string.find(item,'n-',0,true)==1 or string.find(item,'l-',0,true)==1 then autoDotTag=string.sub(item,0,1) item=string.sub(item,3) end if item == 'image' then shapeResult[nval].shape = 'image:' shapeResult[nval].shapeFile =multiCheck(args,'mark',argval,'Red pog.svg') shapeWidth=-1 else shapeResult[nval].shape = item end item= multiCheck(args,'mark-size',argval,'14px') local a,b,c= getsize(string.gsub(string.gsub(item,',','px')..'px','pxpx','px')) if b==a and args['mark-dim'..argval] then b= b / tonumber(string.match(args['mark-dim'..argval],"[%.%-?%d]+")) end shapeHeight=b/2 item=tostring(a)..'px'..tostring(b)..'px'..tostring(c)..'px' shapeResult[nval].shapeSize= item itemTab=splitItem(multiCheck(args,'shape-color',argval,'hard red'),2) shapeResult[nval].shapeColor=itemTab[1] or 'hardred' item=itemCheck(itemTab[2],'%') -- jump through the various opacity hoops and add to color if needed if not item then item=itemCheck(args['shape-opacity'..argval],'%') end if item and item~='0%' and item~='100%' then shapeResult[nval].shapeColor=shapeResult[nval].shapeColor..item end shapeResult[nval].shapeAngle=itemCheck(multiCheck(args,'shape-angle',argval,'0'),'deg') or '0deg' --sort out the outline entry itemTab=splitItem(multiCheck(args,'shape-outline',argval,'transparent,0,100,solid'),4) shapeResult[nval].outlineColor=itemTab[1] or 'dark grey' shapeResult[nval].outlineWidth=itemCheck(itemTab[2],'px') or '1px' if itemTab[3] and itemTab[3]~='100' and itemTab[3]~='0' then shapeResult[nval].outlineColor=shapeResult[nval].outlineColor..itemCheck(itemTab[3],'%') end shapeResult[nval].outlineStyle=itemTab[4] or 'solid' -- label size, background, outline itemTab=splitItem( multiCheck(args,'label-size',argval,'12'),3) shapeResult[nval].textSZ=itemCheck(itemTab[1],'px') or '12px' if itemTab[2]=='outline' then shapeResult[nval].textBG=itemTab[3] or 'transparent' shapeResult[nval].textOL='1px' elseif itemTab[3]=='outline' then shapeResult[nval].textBG=itemTab[2] or 'transparent' shapeResult[nval].textOL='1px' else shapeResult[nval].textOL='0px' shapeResult[nval].textBG=itemTab[2] or 'transparent' end if getColor(shapeResult[nval].textBG)==CTB['hardgrey'] and shapeResult[nval].textBG~='hardgrey' then shapeResult[nval].textBG= 'transparent' end --label color etc itemTab=splitItem(multiCheck(args,'label-color',argval, 'darkgrey','label-colour'),2) shapeResult[nval].textCL=itemTab[1] or 'darkgrey' if itemTab[2] and itemTab[2]~='0%' and itemTab[2]~='100%' then shapeResult[nval].textCL=shapeResult[nval].textCL..itemTab[2] end shapeResult[nval].textSP=itemCheck( multiCheck(args,'label-spacing',argval,'0'),'px') -- sets letter-spacing in px shapeResult[nval].textLH=itemCheck( multiCheck(args,'label-height',argval,'120'),'%') -- sets line height, 120% default shapeResult[nval].textNG=itemCheck(multiCheck(args,'label-angle',argval,'0'),'deg') --sgd=dotTag attributes shapeResult[nval].tagSize=tostring(shapeHeight*1.5)..'px' local c1,c2=checkColors(shapeResult[nval].shapeColor) shapeResult[nval].tagColor=c2 shapeResult[nval].tagSpacer='0px' shapeResult[nval].tagAngle='0deg' -- sge extension line attributes local shapePos=splitItem(multiCheck(args,'label-pos',argval,'right'),6) if shapePos[2]=='with-line' or shapePos[2]=='n-line' then shapeResult[nval].textEW=(shapePos[3] or '1')..'px' -- width shapeResult[nval].textEC=(shapePos[4] or shapeResult[nval].shapeColor or 'darkgrey') elseif shapePos[2]=='photo-panel' then shapeResult[nval].textEW='2px' -- width shapeResult[nval].textEC=shapeResult[nval].textCL else shapeResult[nval].textEW='0px' -- width shapeResult[nval].textEC='grey'-- colour end shapeResult[nval].textES='solid' if argval=='H' then return dotResult end --Assign dot values local dotItem={} dotItem.group=nval dotItem.code=nval dotItem.posType=shapePos[2] or 'nil' if (shapePos[2] or '') =='photo-panel' then dotItem.ppwidth= tonumber(string.match((shapePos[4] or '110'),"%d+")) dotItem.ppheight= tonumber(string.match((shapePos[5] or '48'),"%d+") ) dotItem.photowidth=round(tonumber(string.match((shapePos[3] or '1.3'),"[%.%-?%d]+")) * dotItem.ppheight+1,0) dotItem.photoImage=args['mark-image'..argval] --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end if (shapePos[2] or '') =='mark-line' then local x=tonumber(argval or '1') dotItem.markDest=shapePos[6] or tostring(x-1) dotItem.mlWidth= tonumber(string.match((shapePos[3] or '1'),"%d+")) dotItem.mlStyle= shapePos[4] or 'solid' dotItem.mlGap=tonumber(string.match((shapePos[5] or '0'),"[%d]+")) shapeResult[nval].textEC=shapeResult[nval].outlineColor or 'darkgrey' end if args['mark-coord'..argval] then itemTab=splitItem(convertCoordsTrad (args['mark-coord'..argval]),2) dotItem.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) or 0 else dotItem.lat=tonumber(string.match(args['mark-lat'..argval],"[%.%-?%d]+")) or 0 dotItem.lon=tonumber(string.match(args['mark-lon'..argval],"[%.%-?%d]+")) or 0 end if args['dateline'..argval] and (args['dateline'..argval]=='1' or args['dateline'..argval]=='-1') then dotItem.lon=dotItem.lon+(tonumber(args['dateline'..argval] ) *360) end maplist.lon=dotItem.lon maplist.lat=dotItem.lat dotItem.gridx, dotItem.gridy = maptogrid(maplist,1) local item=args['mark-title'..argval] or '' -- sort out the caption, wikilink and plaintext tooltip items from dotLink if item=='none' then dotItem.param1='nomap nolist' item='' end dotItem.dotLink=item if item ~= '' then item=stripdivs(item) dotItem.title=delink({item}) local linkstart= string.find(item,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then dotItem.dlink=delink({string.sub(item,linkstart,string.find(item,']]',1,true)+1),wikilinks='target'}) else dotItem.dlink='' end else dotItem.dlink='' dotItem.title='' end if autoDotTag=='n' then item=nval elseif autoDotTag=='l' then item=string.char(64+tonumber(nval)) else item='' end dotItem.dotTag = args['numbered'..argval] or item if shapePos[2]=='n-line' and (args['label'..argval] or args['label'..argval]=='') then if dotItem.dlink =='' then item=dotItem.dotTag..' '..args['label'..argval] else item='[['..dotItem.dlink..'|'..dotItem.dotTag..']] '..args['label'..argval] end else item=(args['label'..argval] or '') end if args['labela'..argval] then item = item..'^'..args['labela'..argval] end if args['labelb'..argval] then item = item..'^'..args['labelb'..argval] end local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end dotItem.labelText = a -- convert hats to line breaks if argval=='' then item = (args['label-offset-x']) or (args.ldx) or '0' else item=args['label-offset-x'..argval] or args['ldx'..argval] or args['label-offset-xD'] or args.ldxD or args['label-offset-x1'] or args.ldx1 or '0' end dotItem.dx=tonumber(string.match(item,"[%.%-?%d]+")) if argval=='' then item = (args['label-offset-y']) or (args.ldy) or '0' else item=args['label-offset-y'..argval] or args['ldy'..argval] or args['label-offset-yD'] or args.ldyD or args['label-offset-y1'] or args.ldy1 or '0' end dotItem.dy=tonumber(string.match(item,"[%.%-?%d]+")) dotItem.labelPos=shapePos[1] if args['mark-image'..argval] then dotItem.imageName= args['mark-image'..argval] end dotItem.info=args['mark-description'..argval] or '' table.insert(dotResult,1,dotItem) -- add to start of list, so they are in reverese order for displaying return dotResult end local function tradstyleParseShapes(args,dotTable,dotmax) local sgNumbers,sgSortable={},{} for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) then sgNumbers[x]=x -- add it to the list end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form TODO this must be possible in a single list! if args['mark-coord'] or (args['mark-lat'] and args['mark-lon']) then table.insert(sgSortable,'0') end -- add it to the end of the list local default={} default.D={} local dotResult={} for k,sgnum in pairs(sgSortable) do -- work through the sorted list, parsing each set of shapes in turn, from 1 upwards shapeList[sgnum]={} dotTable=assignTradstyleShape(shapeList,default,dotTable,args,sgnum) end dotTable=assignTradstyleShape(shapeList,default,dotTable,args,'H') -- construct an extra highlight shapeitem return dotTable end local function checkfortooltip (title,dx,dy,dotlabel,dlink,nolabel) -- returns tlink if available, and dlink, if needed and tshape=true if shape needed local tshape,tlink = false,"" if dlink~='' and not nolabel then tlink=dlink end if (tlink=="" or nolabel) and title~="" then tshape=true end -- tshape flags True if title is wanted for shape if (not (dx==0 or dy==0) or dotlabel=='') and title~='' then tshape=true end -- add tooltip to shape if its number has moved return tshape,tlink end local function tshift(angle) -- adjustment to place text near the centre of a triangle, shifted to allow rotation of triangle shape local x=tonumber(string.match(angle,"%-?%d+")) if x<0 then x=360+x end -- set to a single degree direction, 0 to 360 if x>359 then return 0,0 end -- shift the centre of the triangle based on rotation value if x <=40 or x>=320 then return -0.17,0 -- triangle up= -shiftv elseif x>=140 and x<=220 then return 0.17,0 --triangle down= +shiftv elseif x >220 then return 0,-0.17 --triangle left= -shifth elseif x >40 then return 0,0.22 --triangle right= +shifth end return 0,0 end local function makeTriangle(result,row,shape,outline,tlink) local w,h,r=getsize(shape.shapeSize) if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) w=w+p*2 h=h+p*2 end table.insert(result,'<div ') if tlink then table.insert(result,' title="'..row.title..'" ') end table.insert(result,'style="display:inline-block; position: absolute') if shape.shapeAngle ~= '0deg' then table.insert(result,'; transform: rotate('..shape.shapeAngle..')') end local shiftv,shifth=0,0 shiftv,shifth=tshift(shape.shapeAngle) table.insert(result,'; top: '..tostring(row.gridy-h/2+h*shiftv)..'px') table.insert(result,'; left: '..tostring(row.gridx-w/2+w*shifth)..'px; width: 0; height: 0; outline-width: 0px') table.insert(result,'; border-left: '..tostring(w/2)..'px solid transparent') table.insert(result,'; border-right: '..tostring(w/2)..'px solid transparent') if outline then -- fill with outline colour, to make a 'base layer' or shape colour table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.outlineColor).. '">') else table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.shapeColor).. '">') end table.insert(result,'</div>') end local function makeSquare(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) if shape.shape=='square' then h=w end -- squares are always square!. box can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end if r~=0 then div:css('border-radius',tostring(r).."px") end if shape.shape=='panel' then div:css('top', tostring(row.gridy).."px") div:css('left', tostring(row.gridx).."px") else div:css('top', tostring(row.gridy-h/2).."px") div:css('left', tostring(row.gridx-w/2).."px") end div:css('width', tostring(w).."px") div:css('height', tostring(h).."px") div:css('background-color', getColor(shape.shapeColor) ) div:css('color', 'inherit') table.insert(result,tostring(div)) end local function makeCircle(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding if shape.shape=='circle' then h=w end -- circles are always round. ellipse can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end div :css('top', tostring(row.gridy-h/2).."px") :css('left', tostring(row.gridx-w/2).."px") :css('width', tostring(w).."px") :css('height', tostring(h).."px") :css('border-radius', "50%") :css('background-color', getColor(shape.shapeColor) ) :css('color', 'inherit') table.insert(result,tostring(div)) end local function makeRuleA(result,row,shape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local lineV=0 if shape.shape=='rulea' then lineV=oWid*3+16 end table.insert(result,"<div style=\"display:inline-block; position: absolute") -- create a square transparent container, which will rotate line and arrow together table.insert(result,"; top:"..tostring(row.gridy - w/2).."px") table.insert(result,"; left:"..tostring(row.gridx - w/2).."px") table.insert(result,"; width:"..tostring(w).."px") table.insert(result,"; height:"..tostring(w).."px; background:transparent; color:inherit;") table.insert(result,"; transform: rotate( "..tostring(tonumber(string.match(shape.shapeAngle,"[%.%-?%d]+")) - 90).."deg);\">" ) table.insert(result,"<div style=\"display:inline-block; position: absolute") -- put the line (as a border-right) across the container table.insert(result,"; top:"..tostring(lineV).."px") table.insert(result,"; left:"..tostring((w - oWid )/2).."px; width: 0px") table.insert(result,"; height: "..tostring(w -lineV).."px") table.insert(result,"; border-right: "..shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) table.insert(result,"; background:transparent; color:inherit;\"></div>") if shape.shape=='rulea' then table.insert(result,"<div style=\"display:inline-block; position: absolute; top: 0px") --and add arrow head table.insert(result,"; left:"..tostring(w/2-( oWid/2)-oWid*0.55-2).."px; width: 0; height: 0; outline-width: 0px") table.insert(result,"; border-left: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-right: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-bottom: "..tostring(oWid*3+16).."px solid "..getColor(shape.outlineColor).."\"></div>") end table.insert(result,"</div>") end local function makeCurveA(result,row,shape) -- draw a curve with Arrow ----- local w,h=getsize(shape.shapeSize) -- = width,height local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local Angle=tonumber(string.match(shape.shapeAngle,"[%.%d]+")) table.insert(result,'<div style="position: absolute;') --set up out div, which will allow the whole to rotate table.insert(result,'top:'..tostring(row.gridy - (w + oWid*3+16)/2)..'px;') table.insert(result,'left:'..tostring(row.gridx - ( w + oWid*3+16)/2)..'px; ') table.insert(result,'width: '..tostring(w+oWid*3+16)..'px; ') table.insert(result,'height: '..tostring(w+oWid*3+16)..'px; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate( '..tostring(Angle-120)..'deg);">') else table.insert(result,'transform: rotate( '..tostring(Angle -62)..'deg);">') end table.insert(result,'<div style="position: absolute;') --set up div for the rounded corner of a rectangle table.insert(result,'border-left: '..shape.outlineWidth..' '..shape.outlineStyle..' '..getColor(shape.outlineColor)..';') if shape.shape=='curvea' then table.insert(result,'border-radius: 10000px 0 0 '..tostring(w)..'px; top:0px; left:'..tostring(w*0.25)..'px;') else table.insert(result,'border-radius: '..tostring(w)..'px 0 0 10000px;') table.insert(result,'top:'..tostring((oWid*3+16)/2+w*0.15)..'px; left:'..tostring(w*0.25)..'px;') end -- and add a triangular arrow head table.insert(result,'width: '..tostring(w)..'px; height: '..tostring(w)..'px;"></div><div style="position: absolute; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate(180deg); top:'..tostring(w-1)..'px; ') else table.insert(result,'transform: rotate(0deg); top: '..tostring(0-( ( oWid*3+16)/2)+1+( w*0.15) )..'px;') end -- reverse table.insert(result,'left:'..tostring(0-( oWid*0.6)-2+(w*0.25))..'px;') table.insert(result,'width: 0; height: 0; outline-width: 0px; border-left: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-right: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-bottom: '..tostring(oWid*3+16)..'px solid '..getColor(shape.outlineColor)..';"></div></div>') end local function makeLineTo (result,x1,y1,x2,y2,oWid, oStyle, oCol,double) table.insert(result,"<div style=\"display:inline-block; position: absolute;") -- draw a line between x1,y1 and x2,y2, px-coords where 0,0 is centre of frame -- Maths calculations thanks to ES table.insert(result,"left: "..tostring(x1+( (x2-x1)/2) - (math.sqrt( ( x2-x1)^2 + (y2-y1)^2 )/2)-1).."px;") table.insert(result,"top: "..tostring(y1+( ( y2-y1 )/2) ).."px;") table.insert(result,"width: "..tostring(math.sqrt( (x2-x1 )^2 + ( y2-y1 )^2) ).."px;") table.insert(result,"height: "..tostring(double).."px; background-color:transparent; color:inherit; ") table.insert(result,"outline-width: 0; border-bottom: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) if double>1 then table.insert(result,"border-top: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) end if x1==x2 then table.insert(result,"transform: rotate(90deg);") else table.insert(result,"transform: rotate("..tostring(math.atan(( y2-y1)/( x2-x1 ) )*180/math.pi).."deg);\"></div>") end end local function makeClipPath(result,row,shape,outline,tshape) --tshape is a flag to show if the tooltip (title=) is wanted -- return the text css div code to position and draw a shape occupying a specified clippath if not pathshape[shape.shape] then debugmsg(mw.addWarning('shape'..row.code..' = '..shape.shape..'. The shape name is not defined')) return end local w,h,r=getsize(shape.shapeSize) if string.match(shape.shape,"circle") or string.match(shape.shape,"square") then h=w end -- use ellipse and box for stretched shapes local shifth,shiftv = 0,0 if string.match(shape.shape,"triangle") then shiftv,shifth =tshift(shape.shapeAngle) end if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) or 0 w=w+p*2 h=h+p*2 end table.insert(result,"<div ") if tshape then -- Add tooltip if needed table.insert(result," title=\""..row.title.."\" ") end table.insert(result,"style=\"display:inline-block; position: absolute; background-color:") if outline then table.insert(result,getColor(shape.outlineColor)) -- fill with outline colour, to make a 'base layer' else table.insert(result,getColor(shape.shapeColor)) end table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;"..pathshape[shape.shape].."&#39;) ") -- adds the required clippath data from the table of pathshape string literals table.insert(result,"; top:"..tostring(row.gridy - 10 + h*shiftv).."px") table.insert(result,"; left:"..tostring(row.gridx - 10 + w*shifth).."px") table.insert(result,"; width:20px") -- needs to be a path within a 20px20px box, and then rescales using size values to match other shape sizes table.insert(result,"; height:20px; transform:scale("..tostring(w/16)..", "..tostring(h/16)..")") if shape.shapeAngle ~= "0deg" then table.insert(result," rotate("..shape.shapeAngle..")") end table.insert(result,"\"></div>") end local function makeImage(result,row,shape) local w,h,r=getsize(shape.shapeSize) local image=shape.shapeFile if not image or image=='' then image='Red pog.svg' end local imagediv=mw.html.create ('div') imagediv:css('position', "absolute") if shape.shapeAngle ~= "0deg" then imagediv:css('transform',"rotate("..shape.shapeAngle..")") end imagediv :css('top', (row.gridy-1 + math.min(h/2-12,0) - h/2).."px") --File seems to adjust pos for small images :css('left', (row.gridx-1-w/2).."px") :css('background-color', "transparent" ) :css('color','inherit') :wikitext('[[file:'..image..'|'..tostring(w+2)..'px|alt='..(row.title or '')..'|link=]]') table.insert(result,tostring(imagediv)) end local function makePhotoPanel(result,row,shape) local h=row.ppheight table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;' ) table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') table.insert(result, 'width: '..tostring(row.ppwidth)..'px; height: '..tostring(h)..'px; border-radius: 2px; color:inherit;') table.insert(result, 'background-color: #E8E8D6; outline: 2px solid '..getColor(shape.textCL)..'; box-shadow: 2px 2px 4px #33203335;"></div>') if row.photoImage and row.photowidth >0 then table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;') if row.labelPos=='left' or string.find(row.labelPos,'west') then --debugmsg(row.labelText..', Align=right, ppwidth='..tostring(row.ppwidth)..', photowidth='..row.photowidth..', dx='..tostring(row.dx)..', dy='..tostring(row.dy)) table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') row.dx=row.dx+row.photowidth/2 else --debugmsg(row.labelText..', Align=left, ppwidth='..tostring(row.ppwidth)..', photowidth='..row.photowidth..', dx='..tostring(row.dx)..', dy='..tostring(row.dy)) table.insert(result, 'left: '..tostring(row.gridx+row.dx + (row.ppwidth-row.photowidth) - row.ppwidth/2)..'px;') row.dx=row.dx-row.photowidth/2 end table.insert(result, 'background-color:transparent; color:inherit; border-radius: 2px;">') table.insert(result,'[[File:'..row.photoImage..'|x'..tostring(h)..'px|File:'..row.photoImage..']]</div>') row.labelPos='center' end end local function makePanelText(result, row, shape) local w,h,r=getsize(shape.shapeSize) local ty=tonumber(string.match(shape.textSZ or '11',"%d+") ) table.insert(result,'<div style="position:absolute; line-height: 120%; font-size: '..shape.textSZ..'; color:'..getColor(shape.textCL)..';') if row.labelPos == 'left' or string.find(row.labelPos,'west') then table.insert(result,'top: '..tostring(row.dy+row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+3)..'px; text-align: left; width:'..tostring(w)..'px;') elseif row.labelPos == 'right' or string.find(row.labelPos,'east') then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+w-3)..'px; text-align: right; width: max-content; transform: translateX(-100%);') elseif row.labelPos == 'top' or row.labelPos== 'north' then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') elseif row.labelPos == 'bottom' or row.labelPos=='south' then local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*1.1 table.insert(result,'top: '..tostring(row.gridy+w-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') else -- center or centre local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*0.6 table.insert(result,'top: '..tostring(row.gridy+(h/2)-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') end if shape.textSP and shape.textSP ~='0px' then table.insert(result,"letter-spacing: "..shape.textSP..';') end if shape.textLH and shape.textLH ~='120%' then table.insert(result,"line-height: "..shape.textLH..';') end table.insert(result,"vertical-align: middle;\">"..row.labelText.."</div>") end local function makeTextItem(result, row, shape, align, tlink, textItem, dotItem) local w,h,r=getsize(shape.shapeSize) table.insert(result,"<div ") if row.title ~= "" then table.insert(result," title=\""..row.title.."\" ") end local ty,bry,linkoffset = 0,0,0 local compy=0 local lh=tonumber(string.match(shape.textLH or '120',"%d+"))/100 if dotItem==1 or (dotItem==2 and row.posType~='n-line') then -- if there is a dotTag in the middle of the shape then use the tag settings ty=tonumber(string.match(shape.tagSize,"%d+")) or 0 table.insert(result,"style=\"position:absolute; line-height: 120%; top: "..tostring(row.gridy-ty*0.62)) table.insert(result,"px; left: "..tostring(row.gridx).."px; width: fit-content; ") table.insert(result,"text-align: center; color: "..getColor(shape.tagColor).."; background-color: transparent;") local trf="" if shape.tagAngle ~="0deg" then trf=" rotate("..shape.tagAngle..")" end table.insert(result, "transform: translateX(-50%)"..trf.."; font-size: "..shape.tagSize..";") if shape.tagSpacer~='0px' then table.insert(result, "letter-spacing:"..shape.tagSpacer..";") end else -- or add tfx settings for left, right or center align, colors, backgrounds, border-outline table.insert(result,'style="position:absolute; ') if dotItem==2 then -- dotTag is out at x,y so 85% ty=tonumber(string.match(shape.tagSize or '0',"%d+")) table.insert(result,'font-size: '..shape.tagSize..'; padding:0px 2px;line-height: 85%; top: '..tostring(row.dy+row.gridy-ty*0.52)) else -- it is labelText, so use textLH or 120% ty=tonumber(string.match(shape.textSZ or '11',"%d+")) if row.labelPos=='northwest' or row.labelPos=='northeast' then compy=-ty elseif row.labelPos=='southeast' or row.labelPos=='southwest' then compy=ty/2 end if row.labelPos and not (row.labelPos== 'auto' or row.labelPos=='') then bry=(select(2, string.gsub(row.labelText,"<br>", ""))*lh) -- is it a multiline text? expand by line-height /120%? if row.posType == 'with-line' then bry=0 end if row.labelPos=='bottom' or row.labelPos == 'south' then bry = 0 -- and shift by none, all or half elseif row.labelPos=='top' or row.labelPos == 'north' then if shape.shape=='image:' then bry= 1 + math.min(w/2-10,0)-bry*ty else bry= -bry*ty+2 end else bry=-bry*(ty/2 * lh) end end if row.posType == 'photo-panel' then bry=bry+3 end table.insert(result,'font-size: '..shape.textSZ..'; padding:0px 3px;line-height: '..shape.textLH..'; ') table.insert(result,'top: '..tostring(row.dy+row.gridy+compy+bry-ty*lh/2)) end table.insert(result,"px; left: "..tostring(row.dx+row.gridx).."px; color: "..getColor(shape.textCL).."; ") table.insert(result,"width: max-content; ") local trf="" if shape.textNG ~="0deg" then trf="rotate("..shape.textNG..")" end if shape.textOL~="0px" then table.insert(result,"background-color: "..getColor(shape.textBG).."; ") table.insert(result,"border: "..shape.textOL.." solid "..getColor(shape.textCL).."; border-radius:6px;") else table.insert(result,"background-color: transparent;") end if row.labelPos=="right" or string.find(row.labelPos,'east') then table.insert(result,"text-align: left; ") linkoffset=w if shape.textNG ~="0px" then table.insert(result,"transform-origin: left; transform: rotate("..shape.textNG.."); ") end elseif row.labelPos=="left" or string.find(row.labelPos,'west') then if shape.textNG ~="0px" then table.insert(result,"transform-origin: right;") end linkoffset=-w table.insert(result,"text-align: right; transform: translateX(-100%) "..trf.."; ") else table.insert(result,"text-align: center; transform: translateX(-50%) "..trf.."; ") end table.insert(result,'font-weight: normal; line-height: '..shape.textLH..'; letter-spacing:'..shape.textSP..'; vertical-align: bottom;') end if string.find(shape.textAT or '','bold') then textItem='<b>'..textItem..'</b>' end if string.find(shape.textAT or '','italic') then textItem='<i>'..textItem..'</i>' end if shape.textOL=='0px' and shape.textBG~='transparent' and dotItem==0 then table.insert(result,'\"><span style=\"background-color: '..getColor(shape.textBG)..'; color:inherit;\">'..textItem..'</span></div>') else table.insert(result,"\">"..textItem.."</div>") end if tlink~='' and not string.match(row.param1 or "","nolink") then table.insert(result,makeLinkBox(row.gridx+row.dx+linkoffset, row.gridy+row.dy+bry, 16, row.dotTag..' '..row.title,tlink)) end end local function getshapetable(row,shape) -- Construct CSS divs for a dot from shape and map data local result={} local w,h,r=getsize(shape.shapeSize) local tshape,tlink=checkfortooltip(row.title,row.dx,row.dy,row.dotTag,row.dlink,string.match(row.param1 or "","nolink") ) local align=row.labelPos or '' local offsetx,offsety=0,0 local ty=tonumber(string.match((shape.tagSize or 9),"%d+" )) if row.labelText and row.labelText~='' then ty=tonumber(string.match((shape.textSZ or 11),"%d+" )) else align='center' end -- it is just for dotTag, so justify center -- identify align value and extend offsets local widthzone,heightzone = w/2+4,h/2+4 local theta,r = math.deg( math.atan2(row.dy, row.dx)) , math.sqrt(row.dx^2 + row.dy^2) if align=='auto' or align=='' then if (theta < -112 or theta > 112) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "left" offsetx=1 elseif (theta > -68 and theta < 68) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "right" offsetx=-1 elseif theta <0 then offsety=ty/2-1 -- bottom else offsety=0-ty/2+1 -- top end elseif align=='left' or string.find(align,'west') then row.dx=row.dx - w/2 widthzone=4 offsetx=-1 elseif align=='right' or string.find(align,'east') then row.dx=row.dx + w/2 widthzone=4 offsetx=1 offsety=-ty/2 +1 elseif align=='top' or align=='north' then if string.find(shape.shape,'curve') then row.dy=row.dy-(h/2)-12 heightzone=4 offsety=ty/2-1 else row.dy=row.dy - h-2 heightzone=4 offsety=ty/2-1 end elseif align=='bottom' or align=='south' then if string.find(shape.shape,'curve') then row.dy=row.dy+(h/2) heightzone=4 offsety=ty/2-1 else row.dy=row.dy + h+2 heightzone=4 offsety=0-ty/2-1 end end if r > widthzone and shape.textEW ~= "0px" and not string.match(row.param1 or "","noline") then makeLineTo(result, row.gridx+1, row.gridy-1, row.gridx+row.dx+offsetx, row.gridy+row.dy+offsety, shape.textEW, shape.textES,shape.textEC,1) end if row.posType and row.posType=='mark-line' then if row.gridx2 and row.gridy2 then makeLineTo(result, row.gridx, row.gridy, row.gridx2, row.gridy2, tostring(row.mlWidth)..'px', row.mlStyle, getColor(shape.textEC),row.mlGap) -- debugmsg('makeLineTo line drawn from '..row.code..' with width '..tostring(row.mlWidth)..'px '..row.mlStyle..' and color '..getColor(shape.textEC)) end end if w ~= 0 then if shape.shape=='itriangle' then shape.shape='triangle' shape.shapeSize=tostring(w)..'px'..tostring(w/2)..'px' end if shape.shape=="triangle" then if shape.outlineWidth ~= "0px" then makeTriangle(result,row,shape,true,false) -- larger triangle to give the outline, if required end makeTriangle(result,row,shape,false,tshape) -- smaller triangle to fit over the top elseif shape.shape=="square" or shape.shape=="box" or shape.shape=='panel' then makeSquare(result,row,shape,tshape) elseif shape.shape=="circle" or shape.shape=="ellipse" then makeCircle(result,row,shape,tshape) elseif string.find(shape.shape,'image:')==1 then makeImage(result,row,shape) elseif shape.shape=="rulea" or shape.shape=='rule' then makeRuleA(result,row,shape) elseif shape.shape=="curvea" or shape.shape=="curvec" then makeCurveA(result,row,shape) else -- use a pathshape clipPath if shape.outlineWidth ~= "0px" then makeClipPath(result,row,shape,true,false) -- larger path-shape to give the outline, if required end makeClipPath(result,row,shape,false,tshape) end end if row.ppwidth and row.ppwidth>0 then makePhotoPanel(result,row,shape) end if shape.shape=='panel' and row.labelText then makePanelText(result, row, shape) else if row.dotTag and row.dotTag ~= "" then -- there is a dotTag if (row.dx==0 and row.dy==0) and w>0 then -- it is on the dot so if dotsize is not 0 any label is ignored makeTextItem(result, row, shape, align, tlink, '<b>'..row.dotTag..'</b>', 1) else if row.labelText and row.labelText~='' then -- tag and label both used makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.labelText, 0) else makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.dotTag, 2) -- tag is ouside the dot end end else if (row.labelText and row.labelText~='') then -- just the label. No tag makeTextItem(result, row, shape, align, tlink, row.labelText, 0) end end end if tlink and tlink~='' then table.insert(result,makeLinkBox(row.gridx, row.gridy,w+3,row.dotTag..' '..row.title,tlink)) end return table.concat(result) end local function getmapframecontent(args,use) local result, comma= {}, '' local propertyTable= {} local color=splitItem(string.lower( args['map-data-color'] or 'dark orange'), 2) if use == 'basemap' then table.insert(result, '[') propertyTable.stroke = "#000000" propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') propertyTable['stroke-opacity'] = tonumber(color[2] or '15')/100 else propertyTable.title = args['map-data-text'] or args['map-data'] or '' propertyTable.stroke = getColor(color[1]) propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') propertyTable['stroke-opacity'] = tonumber(color[2] or '100')/100 end if args['map-data-inverse'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geomask', ids = args['map-data-inverse'], properties = {title = (args['map-data-text'] or ''), stroke = '#555555', fill = '#555555', ['fill-opacity'] = 0.1, ['stroke-width'] = 1, ['stroke-opacity'] = 0.5 } } table.insert(result, mapJson) comma=', ' end if args['map-data-heavy'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-heavy'], properties = { title = args['map-data-heavy'], stroke = propertyTable.stroke, ['stroke-width'] = 9, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.25 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data-light'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-light'], properties = { title = args['map-data-light'], stroke = propertyTable.stroke, ['stroke-width'] = 3, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.25 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data'] then propertyTable['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.1 local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data'], properties = propertyTable } table.insert(result, comma .. mapJson) comma=', ' end if args['map-wdqs'] then -- inserts a SPARQL wdqs query to find geopoints, geoshapes etc from wikidata local mapSparql = mw.text.jsonEncode{ type = 'ExternalData', service = args['map-wdqs-type'] or 'geopoint', query = args['map-wdqs'], } table.insert(result, comma .. mapSparql) -- debugmsg('mapSparql = ' .. mapSparql) end if args['map-raw'] then table.insert(result, comma .. args['map-raw']) end if use=='basemap' then table.insert(result,']') end return table.concat(result) end --eg | minilocator=filename,bottom right,132px153px, 38%,60%, 22px local function makeLocatorMap (args, result) local miniFile,pos,itemlist,miniW,miniH, miniX,miniY,miniBox, miniBH if args['mini-locator'] then pos,miniFile=extractItem(args['mini-locator']) -- first item is filename, in quotes if it includes commas itemlist=splitItem(pos,5) -- put items in a table filename removed, position,WpxHpx, x%y%,box pos=itemlist[2] or 'right' miniW,miniH=getsize(itemlist[3]) miniX=tonumber(string.match(itemlist[4] or '0','[%d]+'))*miniW/100 miniY=tonumber(string.match(itemlist[5] or '0','[%d]+'))*miniH/100 miniBox=tonumber(string.match(itemlist[6] or '0','[%d]+')) miniBox=miniBox*miniW/100 miniBH=miniBox * maplist.height/maplist.width elseif args['mini-file'] then miniFile = args['mini-file'] pos=string.lower(args.minimap or 'right') if pos=='file' then pos='right' end miniW,miniH = tonumber(args['mini-width'] or 60), tonumber(args['mini-height'] or 60) -- find top left corner of locator miniBox,miniBH=tonumber(args['minimap-boxwidth'] or '0'),0 -- firm up pos offsets for dot (with % or not) and boxsize if any, miniX, miniY=args['minipog-gx'],args['minipog-gy'] if not miniX then miniX=tonumber(args['minipog-x'] or '0') else miniX=tonumber(miniX)*tonumber(miniW)/100 end if not miniY then miniY=tonumber(args['minipog-y'] or '0') else miniY=tonumber(miniY)*tonumber(miniH)/100 end if args['minipog-gx'] then miniBox=miniBox*miniW/100 end miniBH=miniBox * maplist.height/maplist.width else return end local miniTop,miniLeft=0,1 if not string.find(pos,'top') then --only use top left, as link box is in top right or put bottom left or right miniTop=maplist.height+2-miniH if string.find(pos,'right') then miniTop=miniTop-15 -- to avoid (c) line miniLeft=maplist.width-1-miniW end end table.insert(result,'<div style="position: absolute; outline-width: 1px; outline-style: solid; outline-color: white;') table.insert(result,'top: '..tostring(miniTop)..'px; left:'..tostring(miniLeft)..'px; width:'..tostring(miniW)..'px;' ) table.insert(result,'background-color:transparent; color:inherit;">[[File:'..miniFile..'|'..tostring(miniW)..'px|File:'..miniFile..']]</div>') if miniX and miniX>0 then if args['minipog-y'] then miniY=miniY/1.04 end if args['minipog-x'] then miniX=miniX/1.04 end if miniBox<1 then table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-3-6)..'px; left:'..tostring(miniX+miniLeft-3)..'px;') table.insert(result, 'width: 6px; background-color:transparent; color:inherit;">[[File:Red pog.svg|6px|link=]]</div>') else table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-miniBH/2)..'px; left:'..tostring(miniX+miniLeft-miniBox/2)..'px;') table.insert(result, 'width: '..tostring(miniBox)..'px; height:'..tostring(miniBH)..'px; outline:1px solid #AA1205; background-color:#AA120522; color:inherit;"></div>') end end end local function makeArcText(args,result,nval) local items, itemlist='',{} local arcText='' if args['arc'..nval] then items=convertCoords (args['arc'..nval]) items,arcText=extractItem(items) -- first item is text, in quotes if it includes commas itemlist=splitItem(items,9) -- put items in a table: text, lat,lon,size,color,angle,gap,radius,ellipse if itemlist[4]=='' then itemlist[4]='12' end itemlist[4]=string.match((itemlist[4] or '12'),"[%.%-?%d]+") if itemlist[5]=='' then itemlist[5]='grey' end itemlist[6]=string.match((itemlist[6] or '0'),"[%.%-?%d]+") itemlist[7]=string.match((itemlist[7] or '0'),"[%.%-?%d]+") itemlist[8]=string.match((itemlist[8] or '0'),"[%.%-?%d]+") end if args['arc-coord'..nval] then local itemTab=splitItem(convertCoordsTrad (args['arc-coord'..nval]),2) maplist.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) maplist.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) else maplist.lat=tonumber(string.match(args['arc-lat'..nval] or itemlist[2] or '0',"[%.%-?%d]+")) maplist.lon=tonumber(string.match(args['arc-lon'..nval] or itemlist[3] or '0',"[%.%-?%d]+")) end local arcX,arcY=maptogrid(maplist,6) arcText = args['arc-text'..nval] or arcText local fontSize =tonumber(args['arc-text-size'..nval] or itemlist[4] or '12') local textColor=getColor(string.gsub(args['arc-text-color'..nval] or itemlist[5] or 'grey','[%s]+','') ) local arcAngle= tonumber((args['arc-angle'..nval]) or (itemlist[6]) or '45')-90 local arcRadius =tonumber(args['arc-radius'..nval] or itemlist[8] or '0.05') local arcGap = tonumber(args['arc-gap'..nval] or itemlist[7] or '1')* ( ( math.sin(8-math.rad(arcRadius))^8 )+0.4 )*( ( fontSize+6 )/15 ) arcRadius=450*arcRadius*0.75 local ellipseFactor=tonumber(args['ellipse-factor'..nval] or itemlist[9] or '1') local arcRotate =arcAngle+90 if arcGap<0 then arcRotate=arcAngle-90 end local latF=arcY - fontSize + (0-(math.sin(math.rad(arcAngle))) * arcRadius) local lonF=arcX - fontSize +(0-(math.cos(math.rad(arcAngle))) * arcRadius) local step=1 for codepoint in mw.ustring.gcodepoint( arcText ) do -- block step=1,#arcText do table.insert(result,'<div style="position: absolute;') local posY=tostring(round( (latF + (math.sin(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius)) *ellipseFactor,2))..'px;' local posX=tostring(round( (lonF + (math.cos(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius))/ellipseFactor,2))..'px;' table.insert(result,' top: '..posY..' left: '..posX..' transform: rotate( '..tostring(round(arcRotate +((step-1)*arcGap)),2)..'deg);') table.insert(result,'width:'..tostring(fontSize*2)..'px; text-align: center; background-color:transparent; color: '..textColor..';') table.insert(result,'vertical-align: baseline; font-size: '..tostring(fontSize)..'px;">'..mw.ustring.char(codepoint)..'</div>') step=step+1 end end local function makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) local item={} itemdescription=stripdivs(itemdescription or '') local templon=lon if lon > 180 then templon=lon-360 end --for hemisphere+ or -1 dots if lon < -180 then templon=lon+360 end -- use 'real' coordinates for geohack label, while retaining shifted coords for plot if itemcolor=='transparent' then itemcolor='white' end itemcolor=getColor(itemcolor) -- ensure no opacity, which breaks maplink if string.find(itemcolor,'#')==1 and #itemcolor>7 then itemcolor=string.sub(itemcolor,1,7) end table.insert(item, '{ "type": "Feature", "properties": {') table.insert(item, ' "title": "'..itemtitle..'",') table.insert(item, ' "description": "'..itemdescription) table.insert(item, ' ([https://geohack.toolforge.org/geohack.php?params='..tostring(lat)..';'..tostring(templon)) table.insert(item, '_dim:2000 '..tostring(lat)..','..tostring(templon)..'])",') table.insert(item, ' "marker-symbol": "-number-'..string.gsub(group,'%W','')..'", "marker-size": "medium", "marker-color": "'..itemcolor..'" },') table.insert(item, ' "geometry": {"type": "Point", "coordinates": ['..tostring(lon)..','..tostring(lat)..'] } }') return table.concat(item) end local function makeLegendBox(result,args) local legend ={} local line,count, maxWidth='',1,8 local item line,item=extractItem(args.legendBox or '') local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end legend.Text = a -- convert hats to line breaks line=splitItem(line,6) -- (text, size, poition,background color, text/outline color, param options) legend.Size=line[2] or '150px80px1px' legend.Pos=line[3] or '10px10px' legend.Background=line[4] or 'beigeground' legend.Color=line[5] or 'darkbrown' legend.Param= line[6] or '' local argnum,legendCount,titleHeight='1',1,0 if (legend.Text and legend.Text~='') then titleHeight=15+(13.4*(select(2, string.gsub(legend.Text,"<br>", "")))) end local legendLine,legendGroup,legendY={},{},{} while args['legendItem'..argnum] do -- assign legendLine, legendGroup, legendY for each dot line,legendLine[legendCount] = extractItem(args['legendItem'..argnum] or '') line=splitItem(line,3) legendGroup[legendCount]=line[2] or argnum if shapeList[legendGroup[legendCount]] then maxWidth=math.max(tonumber(string.match(shapeList[legendGroup[legendCount]].shapeSize or '10','[%d]+')),maxWidth) else maxWidth=math.max(tonumber(string.match(shapeList['1'].shapeSize or '10','[%d]+')),maxWidth) end maxWidth=maxWidth+1 if line[3] then legendY[legendCount]=tonumber(string.match(line[3],'[%d]+')) else legendY[legendCount] = 3+maxWidth*(legendCount-1)+titleHeight -- if (legend.Text and legend.Text~='') then legendY[legendCount]=legendY[legendCount]+15 end end legendCount=legendCount+1 argnum=tostring(legendCount) end local w,h,r=getsize(legend.Size or '') local x,y=getsize(legend.Pos or '') local div=mw.html.create ('div') div:css('position', 'absolute') div:css('outline', '1px solid'..getColor(legend.Color)) if r~=0 then div:css('border-radius',tostring(r).."px") end div :css('top', y.."px") :css('left', x.."px") :css('width', w.."px") :css('height', h.."px") :css('line-height','105%') :css('background-color', getColor(legend.Background) ) :css('color','inherit') if not string.find(legend.Param,'noshadow') then div:css('box-shadow', '2px 2px 4px #33203335') end div:tag( 'div' ) :css('position', 'absolute') :css('top','1px') :css('left', (w/2).."px") :css('width',(w-8)..'px') :css('text-align', 'center') :css('color', getColor(legend.Color)) :css('transform', 'translateX(-50%)') :css('font-size','11px') :wikitext(legend.Text) :done() for lct=1,legendCount-1 do --local t=legendGroup[lct] local shape=shapeList[legendGroup[lct]] or shapeList['1'] local row={} row.gridx=3+maxWidth/2 row.gridy=(legendY[lct] or 0) + 5 if shape.shape=='image:' then row.gridy=row.gridy+6 end row.dx=0 row.dy=0 local legendShape= getshapetable(row,shape) div:wikitext(legendShape) :tag( 'div' ) :css('position', 'absolute') :css('top',(legendY[lct] or 0)..'px') :css('left', (maxWidth+6)..'px') :css('width', (w-maxWidth-6)..'px') :css('text-align', 'left') :css('line-height','103%') :css('color', getColor(legend.Color)) :css('font-size','10px') :wikitext(legendLine[lct]) :done() end div:allDone() table.insert(result,tostring(div)) end function p._main ( args ) local result={} local frame=mw.getCurrentFrame() local dotTable={} local magVal,scaleVal = args.magnify,'' local origH,origW=maplist.height,maplist.width if magVal then --set up the values needed to magnify the top-right portion of the map magVal=tonumber(string.match(magVal or '0',"[%.%-?%d]+")) or 1 if magVal>1 and magVal <=2 then maplist.height= round(maplist.height/magVal,0) maplist.width=round(maplist.width/magVal,0) scaleVal='transform: scale('..magVal..') translateY('..tostring((origH-maplist.height)/2)..'px);' else magVal=1 end --debugmsg('magVal='..magVal..', Magnify: H='..maplist.height..', ('..origH..'), W='..maplist.width..', ('..origW..'), scale='..scaleVal) --debugmsg('top: '..tostring(((origW-maplist.width))/2)..'px;right: '..tostring(0-((origW-maplist.width))/2)..'px; (alt top: '..tostring(((origH-maplist.height))/2)..'px;)') end -- set up the three nested div boxes (plus an extra if centered) to put the map plus title/caption area, in an appropriate frame on the page if args.float=='center' or args.float=='centre' then table.insert(result,'<div class="center"><div class="thumb tnone">') elseif args.float=='left' then table.insert(result,'<div class="thumb tleft">') else table.insert(result,'<div class="thumb tright">') end table.insert(result,'<div class="thumbinner" style="position: relative; top: 0px; right: 0px; width: '..(args.width or "400")..'px;">') if args.title then table.insert(result,'<div class="center" style="font-weight:bold">'..args.title..'</div>') end if magVal and magVal >1 and magVal<=2 then table.insert(result,'<div class="thumbinner noresize" style="display:block; position: relative; outline:0px; border:0px; padding:0px; background-color:transparent; color:inherit;') table.insert(result,'top: 50%; right: '..tostring(0-((origW-maplist.width))/2)..'px; ') table.insert(result,'height: '..origH..'px; width: '..maplist.width..'px; '..scaleVal..'">') else magVal=1 table.insert(result,'<div class="thumbinner noresize" style="position: relative; outline:0px; border:0px; padding:0px;') table.insert(result,'top: 0px; right: 0px; ') table.insert(result,'height: '..maplist.height..'px; width: '..maplist.width..'px ">') end -- Create the basemap using mapframe local mapframecontent=getmapframecontent(args,'basemap') table.insert(result, frame:extensionTag{ name ='mapframe', content=mapframecontent, args={width=tostring(maplist.width), height=tostring(maplist.height), zoom=tostring(maplist.zoom), longitude=tostring(maplist.lonbase), latitude=tostring(maplist.latbase), mapstyle=maplist.mapstyle, frameless=true } } ) --Add coverall box to block the unhelpful links from mapframe - which wouldn't include all the dots. Reinstate some links to osm and wikimedia table.insert(result,'<div style="position: absolute;width:'..tostring(maplist.width)..'px; height:'..tostring(maplist.height)..'px;') table.insert(result,'top:0px;left:0px;background-color:#FFFFFF00; color:inherit;"></div>') --Add replacent hover-links for OpenStreetMap and maps terms and conditions table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-13)..'px; width: 12px; height: 12px">') table.insert(result,'[[file:Transparent.svg|12px|link=https://www.openstreetmap.org/copyright|'..aboutOSM..']]</div>') table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-110)..'px;') table.insert(result,'width: 12px; height: 12px background-color: transparent; color:inherit;">') table.insert(result,'[[file:Transparent.svg|12px| link=https://foundation.wikimedia.org/wiki/Policy:Maps_Terms_of_Use|'..termsOfUse..']]</div>') -- Add scale-line if not args.scalemark or args.scalemark~='0' then local top=maplist.height-42 local left=maplist.width-61-(tonumber(args.scalemark or '1')) local minipos=string.lower(args.minimap or '') -- scalemark gets pushed left if it would be behind the minimap if minipos~='' and not(string.find(minipos,'left') or string.find(minipos,'top') ) then local offset=tonumber(args.scalemark or '1')-tonumber(args['mini-width'] or '60') if offset<1 then left=maplist.width-61-tonumber(args['mini-width'] or '60') end end if maplist.width-left >216 then top=top+14 end -- shunt scaleline down if it is beyond the copyright stuff local scalek,scalem=getScale(maplist.zoom, maplist.latbase, magVal) local magReduce='' table.insert(result,"<div style=\"display:inline-block; position: absolute; background-color: #111111") table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;M0,8 l0,4 l20,0 l0,-4 l-0.3,0 l0,3.7 l-19.4,0 l0,-3.7 z&#39;) ") table.insert(result,"; width:20px") -- path is a 20px20px box, and then rescales if magVal==1 then table.insert(result,"; top:"..tostring(top-1).."px") table.insert(result,"; left:"..tostring(left+16).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5)..", "..tostring(1.5)..")") else -- shrink the scalemark to compensate for magnification table.insert(result,"; top:"..tostring(top-(magVal*0.28)).."px") table.insert(result,"; left:"..tostring(left+(16*(magVal*1.15))).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5*(1/magVal))..", "..tostring(1.5*(1/magVal))..")") magReduce= 'scale('..tostring(1/magVal)..')' end table.insert(result,"\"></div>") table.insert(result,'<div style="position: absolute; top: '..tostring(top)..'px; left: '..tostring(left+47)..'px; font-size: 9.5px; line-height: 126%; width: fit-content;') table.insert(result,'color: #444433; background-color: transparent; text-align: right; transform: '..magReduce..' translateX(-100%);">'..scalek..'<br>'..scalem..'</div>') end --Set up the shapeList and dotList tables, to provide data to go on the map local sgNumbers,sgSortable={},{} --s1,s2 sgNumbers["1"]="1" if args.useFormatStyle and args.useFormatStyle=='shortstyle' then shapeList=ParseShapeTypes (shapeList,args,"1") for argindex,argv in pairs(args) do -- build a list of all the numbered sg's that have been used if string.find(argindex,"sg[a-f,n%d]+") == 1 then -- only look through the sga,sgb, sgc,sgd,sge,sgf and sgn args local x=string.match(argindex,"[%d]+") -- find its number if x and not sgNumbers[x] then sgNumbers[x]=x end -- only add if not already found end if string.find(argindex,"sg[a-f]H") == 1 and highlightNum then highlightOption=true end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form for k,v in pairs(sgSortable) do -- work through the sorted list, parsing each set of sg's in turn, from 1 upwards shapeList=ParseShapeTypes (shapeList,args,v) end if highlightOption==true then shapeList=ParseShapeTypes (shapeList,args,'H') end end local dotList,dotresult,dotItemTable,dotGroupList={},{},{},{} if (not args.useFormatStyle) or args.useFormatStyle=='standardstyle' then local dotmax=0 for indx in pairs(args) do if string.match(indx,'mark%-coord[%d]+') or string.match(indx,'lat[%d]+') then dotmax=math.max(dotmax, tonumber(string.match(indx,"[%d]+"))) end if (indx=='shapeH') or (indx=='shape-colorH') or (indx=='shape-outlineH') or (indx=='label-colorH') then highlightOption=true end end dotItemTable=tradstyleParseShapes(args,dotItemTable,dotmax) for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) then sgNumbers[x]=x -- add it to the list end end else for indx in pairs(args) do if string.match(indx,'dot[%d]+') then table.insert(dotList,indx) end --add the index name for dot1, dot2 etc to dotList end table.sort(dotList,morethan) for indx,dotName in pairs(dotList) do dotresult=ParseData(args,string.match(dotName,'[%d]+') ) -- using each dot number, assign the settings for each dot to a dotresult item line table.insert(dotItemTable,dotresult) -- and store that item line within the dotItemTable end end for arcVal = 65,91 do -- check through args looking for any arcs local arcLetter=string.char(arcVal) if args['arc-text'..arcLetter] or args['arc'..arcLetter] then makeArcText(args,result,arcLetter) end end local dotdivs='' local ddots=0 if dotItemTable[1] then local ddots=(dotItemTable[1].lat or 0)+(dotItemTable[1].lon or 0) end local fgroup='F'..tostring(maplist.latbase+maplist.lonbase+ddots ) local FullscreenList={} local addcomma='' for i,dotitem in pairs(dotItemTable) do -- working throug each dot item, merge the dot and shape values into a full set of css text local dotgroup= dotitem.group or "0" if dotitem.posType=='mark-line' and dotitem.markDest then --find destination xy values for any mark-lines for n,v in pairs(dotItemTable) do if v.code == dotitem.markDest then dotitem.gridx2=v.gridx dotitem.gridy2=v.gridy break end end end local qtype=dotitem.group -- find which shape group each dot has been assigned --debugmsg('dotgroup='..qtype..', sg='..(sgNumbers[qtype] or 'nil')..' , shapeList='..shapeList[qtype].shape) if not sgNumbers[qtype] then qtype="0" end --shapeList[dotitem.group] will give access to the shape values for that dot if highlightNum==dotitem.code and highlightOption==true and shapeList['H'] then table.insert(result, getshapetable(dotitem,shapeList['H'])) else table.insert(result, getshapetable(dotitem,shapeList[qtype])) -- Add the actual css instructions for each dot end if shapeList[dotgroup] and not string.find((dotitem.param1 or ''),'nomap') then -- only add if not excluded with 'nomap' labelText local ftext='' if dotitem.dotTag~='' and not string.match(dotitem.labelText or '','[%d]') then ftext=stripdivs(dotitem.dotTag or '')..' <br>' end if (dotitem.labelText ~= ftext) and dotitem.dotLink =='' then ftext=ftext..' '..stripdivs(dotitem.labelText)..'<br>' end if (dotitem.dotLink) and (dotitem.dotLink ~='') then ftext=ftext..dotitem.dotLink..'<br>' end if dotitem.imageName then ftext=ftext..'[[File:'..dotitem.imageName..'|250px]]' end table.insert(FullscreenList,1, makeFullscreenItem (string.gsub(ftext,"[\n]+"," "), dotitem.info,dotitem.lat,dotitem.lon,fgroup,shapeList[dotgroup].shapeColor)..addcomma ) addcomma=', ' end -- makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) -- Always add to start of list, to reverse the sequence, and separate with commas except for first item, which is now at the end end if args.legendBox then makeLegendBox(result,args) end if args.minimap or args['mini-locator'] then makeLocatorMap(args,result) end -- add tag link and details for fullscreen version addcomma='' if (mapframecontent or '[]') ~= '[]' then addcomma=',' end mapframecontent=getmapframecontent(args,'fullscreen') local contentstart='[ '..mapframecontent..addcomma..'{ "type": "FeatureCollection", "features": [ ' --extra features after first square bracket local contentend=' ] } ]' table.insert(result, '<div style="position: absolute;top: 9px;left: '..tostring(maplist.width-34)..'px">') table.insert(result, '<div style="color: white; opacity:100; font-size: 19px; font-weight:normal; text-align: left;">') table.insert(result, frame:extensionTag{ name ='maplink', content=contentstart..table.concat(FullscreenList)..contentend, args={zoom=tostring(maplist.zoom+1), class='no-icon', frameless='1', latitude=tostring(maplist.latbase), longitude=tostring(maplist.lonbase), --add invisble 'en-spaces' for tooltip text='<div title="'..fullscreenlinktext..'">&nbsp;&nbsp;&nbsp;</div>'} } ) table.insert(result,'</div></div>') -- end of maplink ----- -- add closing div for main map table.insert(result,'</div>') -- collate caption material to go in the outer div class local autocaption=string.lower(args['auto-caption'] or 'no') local autoOff=autocaption:match("(%w+)(.*)") -- select the first word in autocaption and see if it is a negativeAnswer) if args.caption or not negativeAnswer[autoOff] then table.insert(result,'<div class="thumbcaption" style="text-align:left">') if args.caption then table.insert(result,args.caption) end end local columns=tonumber(autoOff:match("[%d]+") or '1') if columns>1 then columns=round(maplist.width/(columns*17), 0) end -- convert from em to px for historical reasons --for k in pairs(dotList) do capchk=capchk..(args["dotlink"..k] or '') end local capchk=nil local captionList = {} for key, value in pairs(dotItemTable) do -- only add an autocaption line if there is both a dotTag and a dotLink line available and it is not marked as nolist if value.dotTag and value.dotTag~='' and (not string.find(value.param1 or '','nolist')) and string.gsub(value.dotLink or '',"%s+","")~='' then table.insert(captionList, {key = key, value = value}) capchk=true end end if capchk and (not negativeAnswer[autoOff]) then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) local myDivision = string.gsub((args.toggletext or toggletext), "%s+", "") if string.find(autocaption,'collaps') then table.insert(result,'<hr><div class="mw-customtoggle-'..myDivision..'">'..(args.toggletext or toggletext)..'</div>') if string.find(autocaption,'collapsed') then table.insert(result,'<div class="mw-collapsible mw-collapsed" id="mw-customcollapsible-'..myDivision..'">') else table.insert(result,'<div class="mw-collapsible" id="mw-customcollapsible-'..myDivision..'">') end end if string.find(autocaption, 'columns=') then columns=string.match(autocaption,'[%d]+',string.find(autocaption, 'columns=') ) end table.insert(result,'<div class="mw-collapsible-content" style="column-count:'..columns..'; column-rule:solid 1px;text-align:left;padding-top:5px">') table.sort(dotList,lessthan) local nval,ngrp='','0' for k,v in pairs(captionList) do nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag ngrp=v.value.group or '0' if v.value.dotLink and v.value.dotLink~='' and nval and nval~='' then local c1,c2 if nval==args.highlight then c1,c2=checkColors(shapeList['H'].shapeColor) else c1,c2=checkColors(shapeList[ngrp].shapeColor) end table.insert(result,'<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;') table.insert(result,'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div> '..v.value.dotLink..'<br>') end end table.insert(result,'</div>') -- end for caption-content div if string.find(autocaption,'collaps') then table.insert(result,'</div>') end -- end for toggle frame end if args.caption or not negativeAnswer[autoOff] then table.insert(result,'</div>') end -- end for whole caption frame table.insert(result,'</div></div>') -- outer two frames if args.float == 'center' or args.float=='centre' then table.insert(result,'</div>') end if args['show-new-format'] == 'hints' then -- provide a 'format hint panel' in the 'Preview Box' local w="<small>Below are some template hints for the 'sga' compressed version, " w=w..'{{tl|OSM Location dots}}. It can use these, instead of the more verbose {{tl|OSM Location map}} parameter format. ' w=w..'Data is divided between a "ShapeGroup" and the "Dots", so that a single shapeGroup can be used for multiple dots on the map. ' previewMsg(w..'(nb. the "group" value can be the number or an assigned name of a shapeGroup)</small>') w='<small>{{tl|OSM Location dots}}: | dot(n)=group,lat,lon,dotTag | ' w=w..'dotlink=link/tooltip | dotlabel=label,position,dx,dy,param1,info | dotpic=filename <br>' w=w.."(nb. param1 options include 'nolink' 'nolist' 'nomap' 'hemisphere-1' 'hemisphere+1', 'noline' - quotes not required, separate with spaces).<br>" w=w..'| sga = Shape,Sizepx,Color,Angledeg | sgb= OutlineWidth,Color,Style | sgc=TextSize,Color,Angle,bold italic <br>' w=w..'| sgd=TagSizepx,Color,Spacer,Angledeg | sge=LineWidth,Color,Style | sgf=TextSpacingpx,LineHeight%,Outlinepx,backgroundColor<br>' w=w..'| sgn=Name (optional, to assign a meaningful name) | sgp=Parent (can be the name or number of the parent shapeGroup. ' previewMsg(w..'Each shapegroup will inherit values from a parent, stretching back to "sga1" and its default values.)</small>') if #pmsg then local dbg={} for i,x in pairs(pmsg) do table.insert(dbg,x..'<br>') end dbg=mw.addWarning(table.concat(dbg)) table.insert(result, dbg) end end if args.coordtest then debugmsg(mw.text.nowiki(args.coordtest)) end for i,x in pairs(msg) do table.insert(result, x..'<br>') end return table.concat(result) end function p.main(frame) local args = getArgs(frame) local itemTab={} maplist.width=tonumber(args.width) or 400 maplist.height=tonumber(args.height) or 300 if args.coord then itemTab=splitItem(convertCoordsTrad (args.coord),2) maplist.latbase=itemTab[1] maplist.lonbase=itemTab[2] else maplist.lonbase=tonumber(args.lon) or 5 maplist.latbase=tonumber(args.lat) or 0 end maplist.zoom=tonumber(args.zoom) or 1 visibleLinks=args.showlinks highlightNum=args.highlight if args.nolabels=='1' then maplist.mapstyle='osm' else maplist.mapstyle='osm-intl' end return p._main(args) end return p d22uwjjuwgzn8pmgx2si9z0kbdmlsv5 204462 204461 2026-07-05T15:12:36Z HaxelKaszëba 17492 204462 Scribunto text/plain require('strict') local delink=require('Module:Delink').delink local getArgs = require('Module:Arguments').getArgs local p = {} local maplist={} local sgNames={} local highlightOption=false local highlightNum local visibleLinks -- This module creates framed maps of anywhere in the world, at the required scale, and enables annotations, -- dots, shapes, lines and other ways to customise the area of the map being shown. It also provides a link -- to an interactive fullscreen version, which has locator dots instead of annotations and shapes. -- This is the 2025 successor module to a wiki-markup template version of 2024, which itself was a successor -- to the 'Graph'/VEGA driven template that was begun in 2016, until the Vega version was switched off in 2023. -- This module is called from template {{OSM Location map}}, which uses the same parameter formats as before. -- In addition it will be possible to use a more concise parameter format using the template {{OSM Location dots}} -- In general the css output from the two formats will be identical, but the the concise version will allow bits of -- greater control over some of the settings. -- see the documentation on the two template pages for details of how to use the mapping features. -- If language customisation is needed, there are text items below that can be translated. Also see the color table -- below with details of how to add additional color names to allow localised alternatives. -- (Translating other language shape-types could be possible, but has not currently been contemplated. -- Parameter name translation would be harder but likely to be possible, ideally still retaining compatibility -- with template calls already written using English). local negativeAnswer={no=1,'0'-1,off=1} local fullscreenlinktext='Click for interactive fullscreen map with links to nearby articles' local toggletext='[Hide/show caption list]' local termsOfUse='Maps: terms of use' local aboutOSM='About OpenStreetMaps' local shapeList={} --This sets up the 'factoryDefault' shape group 0 shapeList["0"]={shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border of the shape outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items text settings for labels textSZ="11px", textCL="darkgrey", textNG="0deg", --sgf further text settings textSP="0px", textLH="120%", textOL="0px", textBG="transparent", --sgd items for dotTag text settings tagSize="10px", tagColor="white", tagSpacer="0px", tagAngle="0deg", --sge items for extension line to connect label to dot textEW="0px", textEC="darkgrey", textES="solid" } local colorList={} -- used by colorLookup to catch unsupported colors (eg 'LimeGreen'), to convert to generic version colorList['green']='hardgreen' -- it could also be added to to include alternative language equivelants, for a quick solution. colorList['red']='hardred' -- colorList ['source'] = target colorList['white']='white' -- converts any color that includes 'source' into its equivelent target colorList['blue']='hardblue' -- note, for translation you can add to this list, rather than replace it, colorList['brown']='brown' -- which would mean existing map definitions in english would also still work, alongside translated ones colorList['grey']='hardgrey' colorList['gray']='hardgrey' colorList['purple']='hardpurple' colorList['orange']='hardorange' colorList['leaf']='hardleaf' --for a more thorough translation, you can add all the variants of the colors as further CTB elements and hex values or redirects local CTB={} -- set up a table of color names (the CTB Color table index) and html hash colorhex values. CTB["paleblue"],CTB["softblue"],CTB["hardblue"],CTB["darkblue"]="#D6E1EC","#77A1CB","#4B77D6","#1c559e" CTB["palered"],CTB["softred"],CTB["hardred"],CTB["darkred"] = "#FCC6C0","#EC644B","#DB3123","#AA1205" CTB["palegreen"],CTB["softgreen"],CTB["hardgreen"],CTB["darkgreen"]= "#D2F0E5","#81AF81","#269F46","#0b7527" CTB["paleleaf"],CTB["softleaf"],CTB["hardleaf"],CTB["darkleaf"]= "#dff5c1","#b5e376","#8cc244","#679c21" CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"]= "#E8E8D6","#AAAA88","#777755","#444433" CTB["palegray"],CTB["softgray"],CTB["hardgray"],CTB["darkgray"]=CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"] CTB["palebrown"],CTB["softbrown"],CTB["hardbrown"],CTB["darkbrown"]="#FAF6ED","#CCB56C","#AD7F14","#754910" CTB["palepurple"],CTB["softpurple"],CTB["hardpurple"],CTB["darkpurple"]="#e0d1e6","#c784e0","#a029cf","#7a05a8" CTB["paleorange"],CTB["softorange"],CTB["hardorange"],CTB["darkorange"]="#ffedc2","#ffcf61","#EEB533","#e39f05" CTB["black"],CTB["white"],CTB["yellow"]="#000000","#FFFFFF","#FAF039" CTB["background"],CTB["paleground"],CTB["beigeground"]="#f9f6f2","#FEFEFA","#F5F5DC" CTB["beige"]=CTB["beigeground"] CTB["aqua"],CTB["teal"],CTB["fuchsia"] = "#00FFFF","#008080","#FF00FF" CTB["maroon"],CTB["olive"],CTB["navy"] = "#800000","#808000","#000080" CTB["lime"],CTB["limegreen"],CTB["aquamarine"] = "#00FF00","#32CD32","#7FFFD4" CTB["silver"],CTB["yellow"],CTB["orchid"] = "#800000","#FFFF00","#DA70D6" -- set up a table of predefined clip-paths local pathshape={} pathshape.squaredd = "M 19,1.25 l 0,18 -18,0 0,-18 18,0m-1,1 -16,0 0,16 16,0 0,-16m-1,1 0,14 -14,0 0,-14 14,0zm-1,1 -12,0 0,12 12,0 0,-12zm-1,1 0,10 -10,0 0,-10 10,0z" pathshape.squared = "M 18,2.5 l 0,15 -15,0 0,-15 15,0m-1,1 -13,0 0,13 13,0 0,-13zm-1,1 0,11 -11,0 0,-11 11,0z" pathshape.triangledd="M 0 20,20 20,10 0,0 20ZM1.5 19,10 1.7,18.5 19,1.5 19ZM3 18,17 18,10 3.8,3 18ZM4.5 17,10 5.4,15.4 17, 4.5,17ZM6 16,13.8 16,10 7.4z" pathshape.triangled ="M1,18 l 18,0 l -9,-18 l -9,18zm1.7,-1.1 l 7.3,-14.6 l 7.3,14.6 l -14.6, 0zm1.7,-1 l 11.0,0 l -5.5,-11 l -5.5,11z" pathshape.circledd = "M0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm0.8,0a9.2,9.2 0 1,1 18.4,0a9.2,9.2 0 1,1 -18.4,0m1,0a8.2,8.2 0 1,0 16.4,0a8.2,8.2 0 1,0 -16.4,0zm0.8,0a7.2,7.2 0 1,1 14.8,0a7.2,7.2 0 1,1 -14.8,0m1,0 a6.4,6.4 0 1,1 12.8,0a6.4,6.4 0 1,1 -12.8,0z" pathshape.circled = "M2.5,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0m0.8,0 a5,5 0 1,1 11.4,0a5,5 0 1,1 -11.4,0" pathshape.diamond = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10z" pathshape.diamondd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm1,0 l 6,8.5 l 6,-8.5 -6,-8.5 -6,8.5zm1,0 l 5,-7 5,7 -5,7 -5,-7z" pathshape.diamonddd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm0.75,0 l 6.25,9 l 6.25,-9 -6.25,-9 -6.25,9zm0.75,0 l 5.5,-8 5.5,8 -5.5,8 -5.5,-8zm0.75,0 l 4.75,7 l 4.75,-7 -4.75,-7 -4.75,7zm0.75,0 l 4,-6 4,6 -4,6 -4,-6z" pathshape.crossd = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0zM2.3,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.cross = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0z" pathshape.thincross = "M2,12 l6,0 l0,6 l4,0 l0,-6 l6,0 l0,-4 l-6,0 l0,-6 l-4,0 l0,6 l-6,0z" pathshape.fivepointstar = "M10 0 L12.245 6.91 19.511 6.91 13.633 11.18 15.878 18.09 10 13.82 4.122 18.09 6.367 11.18 0.489 6.91 7.755 6.91Z" pathshape.fivepointstard = "M10 1.5 L 11.90825 7.3735 18.08435 7.3735 13.08805 11.003 14.9963 16.8765 10 13.247 5.0037 16.8765 6.91195 11.003 1.91565 7.3735 8.09175 7.3735 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sixpointstar = "M10 0 L12.323 5.977 18.66 5 14.645 10 18.66 15 12.323 14.023 10 20 7.677 14.023 1.34 15 5.355 10 1.34 5 7.677 5.977Z" pathshape.sixpointstard = "M10 1.5 L 11.97455 6.58045 17.361 5.75 13.94825 10 17.361 14.25 11.97455 13.41955 10 18.5 8.02545 13.41955 2.639 14.25 6.05175 10 2.639 5.75 8.02545 6.58045 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sevenpointstar = "M10 0 L12.048 5.747 17.818 3.765 14.602 8.95 19.749 12.225 13.69 12.943 14.339 19.01 10 14.72 5.661 19.01 6.31 12.943 0.251 12.225 5.398 8.95 2.182 3.765 7.952 5.747Z" pathshape.sevenpointstard = "M10 1.5 L11.7408 6.38495 16.6453 4.70025 13.9117 9.1075 18.28665 11.89125 13.1365 12.50155 13.68815 17.6585 10 14.012 6.31185 17.6585 6.8635 12.50155 1.71335 11.89125 6.0883 9.1075 3.3547 4.70025 8.2592 6.38495 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.eightpointstar = "M10 0 L11.88 5.46 17.071 2.929 14.54 8.12 20 10 14.54 11.88 17.071 17.071 11.88 14.54 10 20 8.12 14.54 2.929 17.071 5.46 11.88 0 10 5.46 8.12 2.929 2.929 8.12 5.46Z" pathshape.eightpointstard = "M10 0 L10 1.5 L11.598 6.141 16.01035 3.98965 13.859 8.402 18.5 10 13.859 11.598 16.01035 16.01035 11.598 13.859 10 18.5 8.402 13.859 3.98965 16.01035 6.141 11.598 1.5 10 6.141 8.402 3.98965 3.98965 8.402 6.141ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.ring="M2.6,9.5a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.boxd=pathshape.squared pathshape.boxdd=pathshape.squaredd pathshape.ellipsed=pathshape.circled pathshape.ellipsedd=pathshape.circledd local msg={} local function debugmsg(txt) table.insert(msg,txt) end local pmsg={} local function previewMsg(txt) table.insert(pmsg,txt) end local function colorLookup(color) for c,l in pairs(colorList) do if string.find(color,c) then return l end end return color end local function getColor (color,chk) local c local opacity="100" if not color or color=='' then color='hardgrey' end if color=="transparent" then return color end if color=="background1" then color='background' end if string.byte(color,1,1)==35 and (#color == 7 or #color == 9) then c=color elseif string.byte(color,1,1)==35 and #color == 4 then c=string.sub(color,1,2)..'f'..string.sub(color,3,3)..'f'..string.sub(color,4,4)..'f' else local s=color..'1' s= s:sub(0,s:find("%d")-1) opacity=string.match(color,"%d+") if not CTB[s] then s= colorList[s] end -- check for synonyms and translations if not CTB[s] then debugmsg(mw.addWarning('color = '..color..'. The color name is not defined. Used default grey instead')) end c=CTB[s] or CTB.hardgrey end if opacity and (tonumber(opacity) < 100) and string.find(c,"#")==1 and string.len(c)==7 and opacity~="" then local hexval=string.format("%x",(math.floor(tonumber(opacity)*2.55))) c=c..hexval end return c end function p.colorvalue(frame) -- enable external access to the CTB colorTable values. usage: {{#invoke:OSM Location map|colorvalue|color=hard blue}} local c if not frame.args.color or frame.args.color=='' then c='grey' else c=string.lower(string.gsub(frame.args.color,'%s+','')) end return string.upper(string.sub(getColor(c),2)) end local function checkColors(color) local c=getColor(color,'check') local opacity =1 -- calculate colour brightness and return black or white for contrast if c=='transparent' then return c,'#000000',0 end if not (string.find(c,'#')==1) then return c,'#FFFFFF',0 end if #c>8 then opacity= tonumber('0x'..(string.sub(c,8,9)))/500 end local r=tonumber('0x'..(string.sub(c,2,3)))/255 local g=tonumber('0x'..(string.sub(c,4,5)))/255 local b=tonumber('0x'..(string.sub(c,6,7)))/255 if 0.2126 * r + 0.7152 * g + 0.0722 * b / opacity < 0.7 then return c,'#FFFFFF',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity else return c,'#000000',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity end end local function morethan(a,b) a = tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]') or '0') b = tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]') or '0') return a>b end local function lessthan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) b = tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]') or '0') end return a<b end local function getsize(size) --size1 is between 1 and 3 values, each with px, equating to width,height,corner-rounding --eg '15px 25px 5px' (spaces are optional) or '18px'. returns three numbers local sizeval = {} for v in string.gmatch(size, "[^px]+") do table.insert(sizeval,v) end sizeval[1] = tonumber(sizeval[1]) or 13 sizeval[2] = tonumber(sizeval[2]) or sizeval[1] sizeval[3] = tonumber(sizeval[3]) or 0 return sizeval[1],sizeval[2],sizeval[3] end local function coord2text(coord) -- looks through the output from {{coord}} to find the lat and long decimal values -- and converts compass points to minus or not-minus, return with separating comma. local lat = string.match(coord,'[%.%d]+°[NS]') local lon = string.match(coord,'[%.%d]+°[EW]') local neg={N="",S="-",W="-",E=""} return neg[string.match(lat, '[NS]')]..string.match(lat,'[%.%d]+')..","..neg[string.match(lon, '[EW]')]..string.match(lon,'[%.%d]+') end local function convertCoordsTrad (row) local coords='' if row and string.find (row,'<span class="geo">') then local a,b=string.find(row,'<span class="geo">') local start=b+1 a,b=string.find(row,"</span>",b) local finish=a-1 coords=string.sub(row,start,finish) coords=string.gsub(coords,'; ',',') end return coords end local function convertCoords (row) local start,finish,lat,lon,coords,says if row then local a,b=string.find(row,"<span class=") start=a while a do -- find the final span> finish=b a,b=string.find(row,"span>",b) end if start then coords= string.sub(row,start,finish) says="" if string.find(coords,'<span class="error">') then error("coord error: badly formed coordinates",0) end coords=coord2text(coords) coords = string.sub(row,1,start-1)..coords..string.sub(row,finish+1) else coords=row end return coords else return "Nothing to see here" end end local function fillCommas(val,max) local line='' if not val then line=',' -- ensure there is some content else line = val --string.lower(string.gsub(val,"%s+","")) -- or strip spaces end if string.find(line,',') == 1 then line=' '..line end -- ensure initial comma is not skipped local _, count=string.gsub(line,",","") -- add enough subsequent commas for all entries line=line..string.rep(',',max-count) while(string.find(line,",,") ) do line=string.gsub(line,",,",", ,") --ensure string.gmatch doesn't ignore any empty items by padding with spaces end return line end local function makeLinkBox(left,top,wid,label, link) local linkBoxName='Transparent square.svg' if visibleLinks or '' =='yes' then linkBoxName='Red hollow square.svg' end local builder = mw.html.create('div') --display:inline-block; builder :cssText('position:absolute;left:'..tostring(left-1-wid/2)..'px;top:'..tostring(top-1 + math.min(wid/2-12,0) - wid/2)..'px') :wikitext(string.format( '[[File:%s|%dpx|link=%s|%s]]', linkBoxName, wid+2, link, label )) return tostring(builder) end local function extractItem(row,searchItem) -- remove text following a searchItem or start of line, which might be in quote-marks to allow commas local xend,xstart=1,0 if not row then return '','' end if searchItem then xend,xstart= string.find(row or '',searchItem or 'image:') end if not xstart then return string.gsub(string.gsub(row or '',"%b\"\"", ''),"%b\'\'", '') or '','' end while row:byte(xstart+1) == 32 and xstart<#row do -- skip over any leading spaces xstart=xstart+1 end local xbyte=row:byte(xstart+1) if xbyte == 34 or xbyte == 39 then -- are they wrapped in single or double quotes xstart=xstart+1 xend=row:find(string.char(xbyte),xstart+1) else xend = row:find(',',xstart+1) -- if no quotes, we assume no commas if not xend then xend=#row+1 end end -- return residual row and extracted text return row:sub(0,xstart)..row:sub(xend), row:sub(xstart+1,xend-1) end local function itemCheck(item,ext) if not item then return nil end if not ext then ext='' end return (string.match(item,"[%.%-?%d]+") or '0')..ext end local function stripdivs(line) return string.gsub(line or '',"%b<>", ' ') end local function splitItem(item,max) -- takes a commas-sep list and returns a table of lowercase items with no spaces, or nil local r={} local x=1 item=string.lower(fillCommas(item,max)) for t in string.gmatch(item,"[^,]+") do r[x]=string.gsub(t,"%s+","") if r[x]=='' then r[x]= nil else -- residual items might have commas if x>max then r[max]=(r[max] or '')..', '..r[x] end end x=x+1 end return r end local function ParseShapeTypes (result,args,sgval) -- for use with compressed, comma-separated 'sg plus dots' parameters --[[ shape table items and default values as set at top of page shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items label text textSZ="11px", textCL="white", textNG="0deg", textAT=attributes ("bold" and/or "italic") --sgd items for dotTag tagSize="11px", tagColor="darkgrey", tagSpacer="0px", tagAngle="0deg", --sge extension line textEW="1px", textEC="darkblue", textES="solid" --sgf fx for text labels textSP="0px" textLH="100%" textOL="1px", textBG="paleground", <!--| sga = shape,Size,Color,Angle|sgb= outlineWidth,color,style eg: sga1=circle,14px,blue,0deg| sgb1=0px,dark grey,solid | sgc=textSize,color,angle,bold/italic | sgd=tagSize,Color,Spacer,Angle eg: sgc1=11px,dark grey,0deg,normal| sgd1=9px,white,0px,0deg | sge=lineWidth,color,style |sgf=textspacing,lineHeight%,outlinepx,backgroundcolor, [bold,italic] eg: sge1=0px,black, solid| sgf1=0px,120%,0px,background | dot=shape-group/lat/lon/title/dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> --]] if args["sgn"..sgval] then local sgname=string.match(args["sgn"..sgval],"(%w+)(.*)") sgNames[sgname]=sgval end local parent= args["sgp"..sgval] if parent then parent=string.match(parent,"(%w+)(.*)") local pos= string.find(parent,"%d+") if pos == 1 then parent=string.match(parent,"%d+") else parent=sgNames[parent] or '1' end end if sgval~='H' then if not parent or tonumber(parent) > tonumber(sgval) then if sgval=="1" then parent="0" else parent="1" end end end local itemTab, line, filename result[sgval]={} result[sgval].shapeType=sgval line,filename=extractItem(args['sga'..sgval] or '','image:') if sgval=='1' and not args.sga1 then line='circle,12px,blue,0deg' end -- ensure there is a parent=1 sga result[sgval].shapeFile=filename or '' -- sga= Attributes for shape itemTab=splitItem(line,4) result[sgval].shape = itemTab[1] or result[parent].shape result[sgval].shapeSize=itemTab[2] or result[parent].shapeSize result[sgval].shapeColor=itemTab[3] or result[parent].shapeColor result[sgval].shapeAngle=itemCheck(itemTab[4],'deg') or result[parent].shapeAngle -- sgb= Border outline attributes for shape itemTab=splitItem(args['sgb'..sgval],3) result[sgval].outlineWidth=itemCheck(itemTab[1],'px') or result[parent].outlineWidth result[sgval].outlineColor=itemTab[2] or result[parent].outlineColor result[sgval].outlineStyle=itemTab[3] or result[parent].outlineStyle --sgc=character attributes for label itemTab=splitItem(args['sgc'..sgval],4) result[sgval].textSZ=itemCheck(itemTab[1],'px') or result[parent].textSZ -- size of text in px result[sgval].textCL=itemTab[2] or result[parent].textCL -- colour for text result[sgval].textNG=itemCheck(itemTab[3],'deg') or result[parent].textNG -- Angle for text result[sgval].textAT=itemTab[4] or result[parent].textAT -- attributes bold, and/or italic --sgd=dotTag attributes itemTab=splitItem(args['sgd'..sgval],4) result[sgval].tagSize=itemCheck(itemTab[1],'px') or result[parent].tagSize result[sgval].tagColor=itemTab[2] or result[parent].tagColor result[sgval].tagSpacer=itemCheck(itemTab[3],'px') or result[parent].tagSpacer result[sgval].tagAngle=itemCheck(itemTab[4],'deg') or result[parent].tagAngle --sge= extension line attributes itemTab=splitItem(args['sge'..sgval],4) result[sgval].textEW=itemCheck(itemTab[1],'px') or result[parent].textEW -- width result[sgval].textEC=itemTab[2] or result[parent].textEC -- colour result[sgval].textES=itemTab[3] or result[parent].textES -- style --sgf= fx for label text itemTab=splitItem(args['sgf'..sgval],4) result[sgval].textSP=itemCheck(itemTab[1],'px') or result[parent].textSP -- spacing value for letters result[sgval].textLH=itemCheck(itemTab[2],'%') or result[parent].textLH -- Angle for text result[sgval].textOL=itemCheck(itemTab[3],'px') or result[parent].textOL -- width of text-border line result[sgval].textBG=itemTab[4] or result[parent].textBG -- color for text background return result end local function round(x,dec) -- x=number [, dec=integer] returns numeric value with upto dec decimals (all but first trailing zeros get truncated) if (dec or 0)==0 then return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5) --this avoids .0 where dec=0 end dec =10^(dec) return x>=0 and math.floor(x*dec+0.5)/dec or math.ceil(x*dec-0.5)/dec end local function maptogrid(t,r) --[[ converts mercator projection longitude and latitude coordinates to x and y pixel coordinates, within a frame of given size, centre coordinates and zoom level. t is a table of named indices: {lon, lat, lonbase, latbase, width, height, zoom} output is two values, x and y, rounded to r decimal places--]] local x,y x=t.width/2 + ( ((math.rad(t.lon)*6378137) - (math.rad(t.lonbase)* 6378137) ) / (156543.03*math.cos(t.latbase/180)/(2^t.zoom) ) )*(1-(0.075*(math.abs(t.latbase)/90))) y=t.height/2 + ( ( (math.log(math.tan(math.rad(t.latbase)/2+math.pi/4))*6378137) - (math.log(math.tan(math.rad(t.lat)/2+math.pi/4))*6378137) ) / (156543.03*math.cos(t.latbase/180) / (2^t.zoom) ) )*(1-(0.075*(math.abs(t.latbase)/90))) return round(x,r),round(y,r) --source: python code at https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames and https://wiki.openstreetmap.org/wiki/Mercator --[[ width and height are the size, in pixels, of the map, which will be centerd around lonbase,latbase. Method: Convert lon and lonbase to meter-offsets from coord(0,0), and subtract lonbase from lon, zoom and latbase are used to scale the resulting meter-offset to pixels, and add it to width/2. Convert lat and lat-base to meter-offsets from coord(0,0), subtract lat from latbase, scale the resulting meter-offset to pixels, add it to height/2. A correction factor '*(1-(0.075*(math.abs(t.latbase)/90) ) )' compensates for an error that seems to creep in towards the edges of the map at higher latitudes. It was identified experimentally, and ensures a dot at the edge is in the same place as if that location is positioned at the centre. Original Python code for lat,lon to x,y where 0,0 is the centre of the map print('x=',width+(((math.radians(lon1) * 6378137)- (math.radians(lonbase) * 6378137))/ (156543.03*math.cos(latbase/180)/(2**zoom))),' y=',height+(( (math.log(math.tan(math.pi / 4 + math.radians(latbase) / 2)) * 6378137)- (math.log(math.tan(math.pi / 4 + math.radians(lat1) / 2)) * 6378137))/ (156543.03*math.cos(latbase/180)/(2**zoom))) ) --]] end local function getScale(zoom, lat, magVal) zoom = tonumber(zoom) or 0 lat = tonumber(lat) if not lat then lat = 0 -- fallback end if magVal then magVal = tonumber(magVal) if magVal > 1 and magVal <= 2 then zoom = zoom + (magVal - 1) end end local dist = (156543.03 * math.cos(math.rad(math.abs(lat))) / (2 ^ zoom)) / 17 local y if dist < 1 then y = round(dist * 10, 1) return tostring(y * 100) .. 'm', tostring(round(y * 109, 0)) .. 'yds' elseif dist < 18 then y = round(dist, 0) return tostring(y) .. 'km', tostring(round(y * 0.621371, 1)) .. 'miles' elseif dist < 500 then y = round(dist / 10) return tostring(y * 10) .. 'km', tostring(round(y * 6.21371, 0)) .. 'miles' else y = round(dist / 100) return tostring(y * 100) .. 'km', tostring(round(y * 62.1371, 0)) .. 'miles' end end local function ParseData (args,dotval) -- for use with compressed, comma-separated 'sg plus dots' parameters -- takes a structured series of comma-separated items which get parsed as the following: -- dot(n)= (sgNumber or Name),{{coord}} or (lat,lon), (dotTag) -- dotlink(n) = single-parameter text to give wikilink and/or title used by tootlip, fullscreen dots and autocaption list -- dotlabel(n) = 'label text',pos(left,roght,top,bottom,centre,auto),(dx), (dy) pixel offsets, params, info -- dotpic(n) = single parameter wikimedia filename for an image to use in photopanel and/or fullscreen dots -- dotfeature(n)= 'mark-line' (,linewidth,style,gap) or 'photo-panel' (,image-dim,panel-width,panel-height), draws line to n-1 -- label is used if either a position and/or an x,y offset are not 0,0 ( if no label then dotTag will be put at at the x,y offset, or over the dot -- label text can be autoaligned if x,y puts it left or right of the dot, or centered if above/below) -- quote marks are not needed unless including commas within the label text -- param1 is optional items separated by spaces, and can include [nolabel nolist nomap hemisphere+1 hemisphere-1] -- info is free wikitext, to be used in the fullscreen dot box. (use dotpic to show a picture) --<!--| dotx=shape-group,[lat,lon or {{coord}} ], dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> local result={} local count=1 local row = convertCoords (args["dot"..dotval]) -- swap in any {{coord}} values so they are csv lat and lon row=fillCommas(row,4) result.code=dotval -- store the parameter name as the id code for item in string.gmatch(row,"[^,]+") do -- iterate through 'row', adding each csv item in turn, if present if count==1 then --see if it is a number or a name local pos= string.find(item,"%d+") if pos == 1 then result.group=string.lower(string.gsub(item,"%s+","")) else item=string.match(item,"(%w+)(.*)") -- ensure just a single word result.group=sgNames[item] end elseif count==2 then result.lat=tonumber(string.match(item,"[%.%-?%d]+")) or 0-- find the number, with no non-numeric stuff elseif count==3 then result.lon=tonumber(string.match(item,"[%.%-?%d]+")) or 0 elseif count==4 then result.dotTag=item:match( "^%s*(.-)%s*$" ) or "" -- dotTag allows for internal spaces, but no commas end count=count+1 end row, result.labelText= extractItem(args["dotlabel"..dotval]) result.labelText= string.gsub(result.labelText,"[%^]+","<br>") -- convert hats to line breaks local item=splitItem(row,6) result.labelPos=item[2] or 'center' result.dx=tonumber(string.match(item[3] or '0',"[%.%-?%d]+")) or 0 result.dy=tonumber(string.match(item[4] or '0',"[%.%-?%d]+")) or 0 result.param1=string.lower(item[5] or '') if string.find(result.param1,"hemisphere-1",1,true) then result.lon=result.lon-360 elseif string.find(result.param1,"hemisphere+1",1,true) then result.lon=result.lon+360 end local txt = '' if item[6] then -- ensure all info elements are included, including commas count=1 local max=6 for t1 in string.gmatch(fillCommas(row,max),"[^,]+") do if count>max then txt=txt..',' end if count>=max then txt=txt..t1 end count=count+1 end end result.info=txt result.imageName = args['dotpic'..dotval] -- Get first wikilinked item (if any) from the args.dotlink and set this plus the delinked text local testx=args["dotlink"..dotval] or '' result.dotLink=testx if testx ~= '' then testx=stripdivs(testx) result.title=delink({ testx }) local linkstart= string.find(testx,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then result.dlink=delink( { string.sub(testx,linkstart,string.find(testx,']]',1,true)+1),wikilinks='target' } ) else result.dlink='' end else result.dlink='' result.title='' end if args['dotfeature'..dotval] then local item=splitItem(args['dotfeature'..dotval],6) if (item[1] or '') =='photo-panel' then result.ppwidth= tonumber(string.match((item[3] or '110'),"%d+")) -- panel width result.ppheight= tonumber(string.match((item[4] or '48'),"%d+") ) --panel height result.photowidth=round(tonumber(string.match((item[2] or '1.3'),"[%.%-?%d]+")) * result.ppheight+1,0) -- dimension to set image size result.photoImage=result.imageName result.posType='photo-panel' elseif (item[1] or '') == 'mark-line' then local x=tonumber(dotval or '0') result.markDest=item[5] or tostring(x-1) result.mlWidth= tonumber(string.match((item[2]) or '',"%d+") or '1') result.mlStyle= item[3] or 'solid' result.mlGap=tonumber(string.match((item[4] or ''),"[%d]+") or '0') result.posType='mark-line' -- debugmsg('making line for '..tostring(x)..'to '..tostring(result.markDest)..' with width '..tostring(result.mlWidth)) end --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end maplist.lon=result.lon maplist.lat=result.lat result.gridx, result.gridy = maptogrid(maplist,1) -- convert geo coords to grid xy - using values from maplist table return result end local function multiCheck (args, argName, argVal, defVal, alt) if not alt then alt='nonexistant' end if argVal=='H' and not args[argName..'H'] then argVal=highlightNum or '1' end if argVal=='' then return (args[argName] or args[alt] or (args[argName..'D']) or args[alt..'D'] or defVal) -- unnumbered args do not inherit from D or 1 else return (args[argName..argVal]) or (args[alt..argVal]) or (args[argName..'D']) or (args[alt..'D']) or (args[argName..'1']) or (args[alt..'1']) or defVal end end local function assignTradstyleShape(shapeResult,default,dotResult,args,nval) local item,itemTab local autoDotTag='' local shapeWidth,shapeHeight=0,0 local argval=nval -- to catch the unnumbered shape series if argval=='0' then argval='' end if nval=='H' then shapeResult.H={} end item=string.lower(multiCheck(args,'shape',argval,'image')) if string.find(item,'n-',0,true)==1 or string.find(item,'l-',0,true)==1 then autoDotTag=string.sub(item,0,1) item=string.sub(item,3) end if item == 'image' then shapeResult[nval].shape = 'image:' shapeResult[nval].shapeFile =multiCheck(args,'mark',argval,'Red pog.svg') shapeWidth=-1 else shapeResult[nval].shape = item end item= multiCheck(args,'mark-size',argval,'14px') local a,b,c= getsize(string.gsub(string.gsub(item,',','px')..'px','pxpx','px')) if b==a and args['mark-dim'..argval] then b= b / tonumber(string.match(args['mark-dim'..argval],"[%.%-?%d]+")) end shapeHeight=b/2 item=tostring(a)..'px'..tostring(b)..'px'..tostring(c)..'px' shapeResult[nval].shapeSize= item itemTab=splitItem(multiCheck(args,'shape-color',argval,'hard red'),2) shapeResult[nval].shapeColor=itemTab[1] or 'hardred' item=itemCheck(itemTab[2],'%') -- jump through the various opacity hoops and add to color if needed if not item then item=itemCheck(args['shape-opacity'..argval],'%') end if item and item~='0%' and item~='100%' then shapeResult[nval].shapeColor=shapeResult[nval].shapeColor..item end shapeResult[nval].shapeAngle=itemCheck(multiCheck(args,'shape-angle',argval,'0'),'deg') or '0deg' --sort out the outline entry itemTab=splitItem(multiCheck(args,'shape-outline',argval,'transparent,0,100,solid'),4) shapeResult[nval].outlineColor=itemTab[1] or 'dark grey' shapeResult[nval].outlineWidth=itemCheck(itemTab[2],'px') or '1px' if itemTab[3] and itemTab[3]~='100' and itemTab[3]~='0' then shapeResult[nval].outlineColor=shapeResult[nval].outlineColor..itemCheck(itemTab[3],'%') end shapeResult[nval].outlineStyle=itemTab[4] or 'solid' -- label size, background, outline itemTab=splitItem( multiCheck(args,'label-size',argval,'12'),3) shapeResult[nval].textSZ=itemCheck(itemTab[1],'px') or '12px' if itemTab[2]=='outline' then shapeResult[nval].textBG=itemTab[3] or 'transparent' shapeResult[nval].textOL='1px' elseif itemTab[3]=='outline' then shapeResult[nval].textBG=itemTab[2] or 'transparent' shapeResult[nval].textOL='1px' else shapeResult[nval].textOL='0px' shapeResult[nval].textBG=itemTab[2] or 'transparent' end if getColor(shapeResult[nval].textBG)==CTB['hardgrey'] and shapeResult[nval].textBG~='hardgrey' then shapeResult[nval].textBG= 'transparent' end --label color etc itemTab=splitItem(multiCheck(args,'label-color',argval, 'darkgrey','label-colour'),2) shapeResult[nval].textCL=itemTab[1] or 'darkgrey' if itemTab[2] and itemTab[2]~='0%' and itemTab[2]~='100%' then shapeResult[nval].textCL=shapeResult[nval].textCL..itemTab[2] end shapeResult[nval].textSP=itemCheck( multiCheck(args,'label-spacing',argval,'0'),'px') -- sets letter-spacing in px shapeResult[nval].textLH=itemCheck( multiCheck(args,'label-height',argval,'120'),'%') -- sets line height, 120% default shapeResult[nval].textNG=itemCheck(multiCheck(args,'label-angle',argval,'0'),'deg') --sgd=dotTag attributes shapeResult[nval].tagSize=tostring(shapeHeight*1.5)..'px' local c1,c2=checkColors(shapeResult[nval].shapeColor) shapeResult[nval].tagColor=c2 shapeResult[nval].tagSpacer='0px' shapeResult[nval].tagAngle='0deg' -- sge extension line attributes local shapePos=splitItem(multiCheck(args,'label-pos',argval,'right'),6) if shapePos[2]=='with-line' or shapePos[2]=='n-line' then shapeResult[nval].textEW=(shapePos[3] or '1')..'px' -- width shapeResult[nval].textEC=(shapePos[4] or shapeResult[nval].shapeColor or 'darkgrey') elseif shapePos[2]=='photo-panel' then shapeResult[nval].textEW='2px' -- width shapeResult[nval].textEC=shapeResult[nval].textCL else shapeResult[nval].textEW='0px' -- width shapeResult[nval].textEC='grey'-- colour end shapeResult[nval].textES='solid' if argval=='H' then return dotResult end --Assign dot values local dotItem={} dotItem.group=nval dotItem.code=nval dotItem.posType=shapePos[2] or 'nil' if (shapePos[2] or '') =='photo-panel' then dotItem.ppwidth= tonumber(string.match((shapePos[4] or '110'),"%d+")) dotItem.ppheight= tonumber(string.match((shapePos[5] or '48'),"%d+") ) dotItem.photowidth=round(tonumber(string.match((shapePos[3] or '1.3'),"[%.%-?%d]+")) * dotItem.ppheight+1,0) dotItem.photoImage=args['mark-image'..argval] --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end if (shapePos[2] or '') =='mark-line' then local x=tonumber(argval or '1') dotItem.markDest=shapePos[6] or tostring(x-1) dotItem.mlWidth= tonumber(string.match((shapePos[3] or '1'),"%d+")) dotItem.mlStyle= shapePos[4] or 'solid' dotItem.mlGap=tonumber(string.match((shapePos[5] or '0'),"[%d]+")) shapeResult[nval].textEC=shapeResult[nval].outlineColor or 'darkgrey' end if args['mark-coord'..argval] then local raw = args['mark-coord'..argval] local lat, lon lat, lon = string.match(raw, "{{coord|([%-%.%d]+)|([%-%.%d]+)}}") if not lat or not lon then lat, lon = string.match(raw, "([%-%.%d]+)%s*[,;]%s*([%-%.%d]+)") end if not lat or not lon then local cleaned = convertCoordsTrad(raw) if cleaned then local tab = splitItem(cleaned, 2) lat = tab[1] and string.match(tab[1], "[%-%.%d]+") lon = tab[2] and string.match(tab[2], "[%-%.%d]+") end end dotItem.lat = tonumber(lat) or 0 dotItem.lon = tonumber(lon) or 0 else dotItem.lat = tonumber(string.match(args['mark-lat'..argval] or "", "[%-%.%d]+")) or 0 dotItem.lon = tonumber(string.match(args['mark-lon'..argval] or "", "[%-%.%d]+")) or 0 end if args['dateline'..argval] and (args['dateline'..argval]=='1' or args['dateline'..argval]=='-1') then dotItem.lon=dotItem.lon+(tonumber(args['dateline'..argval] ) *360) end maplist.lon=dotItem.lon maplist.lat=dotItem.lat dotItem.gridx, dotItem.gridy = maptogrid(maplist,1) local item=args['mark-title'..argval] or '' -- sort out the caption, wikilink and plaintext tooltip items from dotLink if item=='none' then dotItem.param1='nomap nolist' item='' end dotItem.dotLink=item if item ~= '' then item=stripdivs(item) dotItem.title=delink({item}) local linkstart= string.find(item,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then dotItem.dlink=delink({string.sub(item,linkstart,string.find(item,']]',1,true)+1),wikilinks='target'}) else dotItem.dlink='' end else dotItem.dlink='' dotItem.title='' end if autoDotTag=='n' then item=nval elseif autoDotTag=='l' then item=string.char(64+tonumber(nval)) else item='' end dotItem.dotTag = args['numbered'..argval] or item if shapePos[2]=='n-line' and (args['label'..argval] or args['label'..argval]=='') then if dotItem.dlink =='' then item=dotItem.dotTag..' '..args['label'..argval] else item='[['..dotItem.dlink..'|'..dotItem.dotTag..']] '..args['label'..argval] end else item=(args['label'..argval] or '') end if args['labela'..argval] then item = item..'^'..args['labela'..argval] end if args['labelb'..argval] then item = item..'^'..args['labelb'..argval] end local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end dotItem.labelText = a -- convert hats to line breaks if argval=='' then item = (args['label-offset-x']) or (args.ldx) or '0' else item=args['label-offset-x'..argval] or args['ldx'..argval] or args['label-offset-xD'] or args.ldxD or args['label-offset-x1'] or args.ldx1 or '0' end dotItem.dx=tonumber(string.match(item,"[%.%-?%d]+")) if argval=='' then item = (args['label-offset-y']) or (args.ldy) or '0' else item=args['label-offset-y'..argval] or args['ldy'..argval] or args['label-offset-yD'] or args.ldyD or args['label-offset-y1'] or args.ldy1 or '0' end dotItem.dy=tonumber(string.match(item,"[%.%-?%d]+")) dotItem.labelPos=shapePos[1] if args['mark-image'..argval] then dotItem.imageName= args['mark-image'..argval] end dotItem.info=args['mark-description'..argval] or '' table.insert(dotResult,1,dotItem) -- add to start of list, so they are in reverese order for displaying return dotResult end local function tradstyleParseShapes(args,dotTable,dotmax) local sgNumbers,sgSortable={},{} for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) then sgNumbers[x]=x -- add it to the list end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form TODO this must be possible in a single list! if args['mark-coord'] or (args['mark-lat'] and args['mark-lon']) then table.insert(sgSortable,'0') end -- add it to the end of the list local default={} default.D={} local dotResult={} for k,sgnum in pairs(sgSortable) do -- work through the sorted list, parsing each set of shapes in turn, from 1 upwards shapeList[sgnum]={} dotTable=assignTradstyleShape(shapeList,default,dotTable,args,sgnum) end dotTable=assignTradstyleShape(shapeList,default,dotTable,args,'H') -- construct an extra highlight shapeitem return dotTable end local function checkfortooltip (title,dx,dy,dotlabel,dlink,nolabel) -- returns tlink if available, and dlink, if needed and tshape=true if shape needed local tshape,tlink = false,"" if dlink~='' and not nolabel then tlink=dlink end if (tlink=="" or nolabel) and title~="" then tshape=true end -- tshape flags True if title is wanted for shape if (not (dx==0 or dy==0) or dotlabel=='') and title~='' then tshape=true end -- add tooltip to shape if its number has moved return tshape,tlink end local function tshift(angle) -- adjustment to place text near the centre of a triangle, shifted to allow rotation of triangle shape local x=tonumber(string.match(angle,"%-?%d+")) if x<0 then x=360+x end -- set to a single degree direction, 0 to 360 if x>359 then return 0,0 end -- shift the centre of the triangle based on rotation value if x <=40 or x>=320 then return -0.17,0 -- triangle up= -shiftv elseif x>=140 and x<=220 then return 0.17,0 --triangle down= +shiftv elseif x >220 then return 0,-0.17 --triangle left= -shifth elseif x >40 then return 0,0.22 --triangle right= +shifth end return 0,0 end local function makeTriangle(result,row,shape,outline,tlink) local w,h,r=getsize(shape.shapeSize) if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) w=w+p*2 h=h+p*2 end table.insert(result,'<div ') if tlink then table.insert(result,' title="'..row.title..'" ') end table.insert(result,'style="display:inline-block; position: absolute') if shape.shapeAngle ~= '0deg' then table.insert(result,'; transform: rotate('..shape.shapeAngle..')') end local shiftv,shifth=0,0 shiftv,shifth=tshift(shape.shapeAngle) table.insert(result,'; top: '..tostring(row.gridy-h/2+h*shiftv)..'px') table.insert(result,'; left: '..tostring(row.gridx-w/2+w*shifth)..'px; width: 0; height: 0; outline-width: 0px') table.insert(result,'; border-left: '..tostring(w/2)..'px solid transparent') table.insert(result,'; border-right: '..tostring(w/2)..'px solid transparent') if outline then -- fill with outline colour, to make a 'base layer' or shape colour table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.outlineColor).. '">') else table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.shapeColor).. '">') end table.insert(result,'</div>') end local function makeSquare(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) if shape.shape=='square' then h=w end -- squares are always square!. box can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end if r~=0 then div:css('border-radius',tostring(r).."px") end if shape.shape=='panel' then div:css('top', tostring(row.gridy).."px") div:css('left', tostring(row.gridx).."px") else div:css('top', tostring(row.gridy-h/2).."px") div:css('left', tostring(row.gridx-w/2).."px") end div:css('width', tostring(w).."px") div:css('height', tostring(h).."px") div:css('background-color', getColor(shape.shapeColor) ) div:css('color', 'inherit') table.insert(result,tostring(div)) end local function makeCircle(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding if shape.shape=='circle' then h=w end -- circles are always round. ellipse can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end div :css('top', tostring(row.gridy-h/2).."px") :css('left', tostring(row.gridx-w/2).."px") :css('width', tostring(w).."px") :css('height', tostring(h).."px") :css('border-radius', "50%") :css('background-color', getColor(shape.shapeColor) ) :css('color', 'inherit') table.insert(result,tostring(div)) end local function makeRuleA(result,row,shape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local lineV=0 if shape.shape=='rulea' then lineV=oWid*3+16 end table.insert(result,"<div style=\"display:inline-block; position: absolute") -- create a square transparent container, which will rotate line and arrow together table.insert(result,"; top:"..tostring(row.gridy - w/2).."px") table.insert(result,"; left:"..tostring(row.gridx - w/2).."px") table.insert(result,"; width:"..tostring(w).."px") table.insert(result,"; height:"..tostring(w).."px; background:transparent; color:inherit;") table.insert(result,"; transform: rotate( "..tostring(tonumber(string.match(shape.shapeAngle,"[%.%-?%d]+")) - 90).."deg);\">" ) table.insert(result,"<div style=\"display:inline-block; position: absolute") -- put the line (as a border-right) across the container table.insert(result,"; top:"..tostring(lineV).."px") table.insert(result,"; left:"..tostring((w - oWid )/2).."px; width: 0px") table.insert(result,"; height: "..tostring(w -lineV).."px") table.insert(result,"; border-right: "..shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) table.insert(result,"; background:transparent; color:inherit;\"></div>") if shape.shape=='rulea' then table.insert(result,"<div style=\"display:inline-block; position: absolute; top: 0px") --and add arrow head table.insert(result,"; left:"..tostring(w/2-( oWid/2)-oWid*0.55-2).."px; width: 0; height: 0; outline-width: 0px") table.insert(result,"; border-left: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-right: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-bottom: "..tostring(oWid*3+16).."px solid "..getColor(shape.outlineColor).."\"></div>") end table.insert(result,"</div>") end local function makeCurveA(result,row,shape) -- draw a curve with Arrow ----- local w,h=getsize(shape.shapeSize) -- = width,height local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local Angle=tonumber(string.match(shape.shapeAngle,"[%.%d]+")) table.insert(result,'<div style="position: absolute;') --set up out div, which will allow the whole to rotate table.insert(result,'top:'..tostring(row.gridy - (w + oWid*3+16)/2)..'px;') table.insert(result,'left:'..tostring(row.gridx - ( w + oWid*3+16)/2)..'px; ') table.insert(result,'width: '..tostring(w+oWid*3+16)..'px; ') table.insert(result,'height: '..tostring(w+oWid*3+16)..'px; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate( '..tostring(Angle-120)..'deg);">') else table.insert(result,'transform: rotate( '..tostring(Angle -62)..'deg);">') end table.insert(result,'<div style="position: absolute;') --set up div for the rounded corner of a rectangle table.insert(result,'border-left: '..shape.outlineWidth..' '..shape.outlineStyle..' '..getColor(shape.outlineColor)..';') if shape.shape=='curvea' then table.insert(result,'border-radius: 10000px 0 0 '..tostring(w)..'px; top:0px; left:'..tostring(w*0.25)..'px;') else table.insert(result,'border-radius: '..tostring(w)..'px 0 0 10000px;') table.insert(result,'top:'..tostring((oWid*3+16)/2+w*0.15)..'px; left:'..tostring(w*0.25)..'px;') end -- and add a triangular arrow head table.insert(result,'width: '..tostring(w)..'px; height: '..tostring(w)..'px;"></div><div style="position: absolute; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate(180deg); top:'..tostring(w-1)..'px; ') else table.insert(result,'transform: rotate(0deg); top: '..tostring(0-( ( oWid*3+16)/2)+1+( w*0.15) )..'px;') end -- reverse table.insert(result,'left:'..tostring(0-( oWid*0.6)-2+(w*0.25))..'px;') table.insert(result,'width: 0; height: 0; outline-width: 0px; border-left: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-right: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-bottom: '..tostring(oWid*3+16)..'px solid '..getColor(shape.outlineColor)..';"></div></div>') end local function makeLineTo (result,x1,y1,x2,y2,oWid, oStyle, oCol,double) table.insert(result,"<div style=\"display:inline-block; position: absolute;") -- draw a line between x1,y1 and x2,y2, px-coords where 0,0 is centre of frame -- Maths calculations thanks to ES table.insert(result,"left: "..tostring(x1+( (x2-x1)/2) - (math.sqrt( ( x2-x1)^2 + (y2-y1)^2 )/2)-1).."px;") table.insert(result,"top: "..tostring(y1+( ( y2-y1 )/2) ).."px;") table.insert(result,"width: "..tostring(math.sqrt( (x2-x1 )^2 + ( y2-y1 )^2) ).."px;") table.insert(result,"height: "..tostring(double).."px; background-color:transparent; color:inherit; ") table.insert(result,"outline-width: 0; border-bottom: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) if double>1 then table.insert(result,"border-top: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) end if x1==x2 then table.insert(result,"transform: rotate(90deg);") else table.insert(result,"transform: rotate("..tostring(math.atan(( y2-y1)/( x2-x1 ) )*180/math.pi).."deg);\"></div>") end end local function makeClipPath(result,row,shape,outline,tshape) --tshape is a flag to show if the tooltip (title=) is wanted -- return the text css div code to position and draw a shape occupying a specified clippath if not pathshape[shape.shape] then debugmsg(mw.addWarning('shape'..row.code..' = '..shape.shape..'. The shape name is not defined')) return end local w,h,r=getsize(shape.shapeSize) if string.match(shape.shape,"circle") or string.match(shape.shape,"square") then h=w end -- use ellipse and box for stretched shapes local shifth,shiftv = 0,0 if string.match(shape.shape,"triangle") then shiftv,shifth =tshift(shape.shapeAngle) end if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) or 0 w=w+p*2 h=h+p*2 end table.insert(result,"<div ") if tshape then -- Add tooltip if needed table.insert(result," title=\""..row.title.."\" ") end table.insert(result,"style=\"display:inline-block; position: absolute; background-color:") if outline then table.insert(result,getColor(shape.outlineColor)) -- fill with outline colour, to make a 'base layer' else table.insert(result,getColor(shape.shapeColor)) end table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;"..pathshape[shape.shape].."&#39;) ") -- adds the required clippath data from the table of pathshape string literals table.insert(result,"; top:"..tostring(row.gridy - 10 + h*shiftv).."px") table.insert(result,"; left:"..tostring(row.gridx - 10 + w*shifth).."px") table.insert(result,"; width:20px") -- needs to be a path within a 20px20px box, and then rescales using size values to match other shape sizes table.insert(result,"; height:20px; transform:scale("..tostring(w/16)..", "..tostring(h/16)..")") if shape.shapeAngle ~= "0deg" then table.insert(result," rotate("..shape.shapeAngle..")") end table.insert(result,"\"></div>") end local function makeImage(result,row,shape) local w,h,r=getsize(shape.shapeSize) local image=shape.shapeFile if not image or image=='' then image='Red pog.svg' end local imagediv=mw.html.create ('div') imagediv:css('position', "absolute") if shape.shapeAngle ~= "0deg" then imagediv:css('transform',"rotate("..shape.shapeAngle..")") end imagediv :css('top', (row.gridy-1 + math.min(h/2-12,0) - h/2).."px") --File seems to adjust pos for small images :css('left', (row.gridx-1-w/2).."px") :css('background-color', "transparent" ) :css('color','inherit') :wikitext('[[file:'..image..'|'..tostring(w+2)..'px|alt='..(row.title or '')..'|link=]]') table.insert(result,tostring(imagediv)) end local function makePhotoPanel(result,row,shape) local h=row.ppheight table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;' ) table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') table.insert(result, 'width: '..tostring(row.ppwidth)..'px; height: '..tostring(h)..'px; border-radius: 2px; color:inherit;') table.insert(result, 'background-color: #E8E8D6; outline: 2px solid '..getColor(shape.textCL)..'; box-shadow: 2px 2px 4px #33203335;"></div>') if row.photoImage and row.photowidth >0 then table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;') if row.labelPos=='left' or string.find(row.labelPos,'west') then --debugmsg(row.labelText..', Align=right, ppwidth='..tostring(row.ppwidth)..', photowidth='..row.photowidth..', dx='..tostring(row.dx)..', dy='..tostring(row.dy)) table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') row.dx=row.dx+row.photowidth/2 else --debugmsg(row.labelText..', Align=left, ppwidth='..tostring(row.ppwidth)..', photowidth='..row.photowidth..', dx='..tostring(row.dx)..', dy='..tostring(row.dy)) table.insert(result, 'left: '..tostring(row.gridx+row.dx + (row.ppwidth-row.photowidth) - row.ppwidth/2)..'px;') row.dx=row.dx-row.photowidth/2 end table.insert(result, 'background-color:transparent; color:inherit; border-radius: 2px;">') table.insert(result,'[[File:'..row.photoImage..'|x'..tostring(h)..'px|File:'..row.photoImage..']]</div>') row.labelPos='center' end end local function makePanelText(result, row, shape) local w,h,r=getsize(shape.shapeSize) local ty=tonumber(string.match(shape.textSZ or '11',"%d+") ) table.insert(result,'<div style="position:absolute; line-height: 120%; font-size: '..shape.textSZ..'; color:'..getColor(shape.textCL)..';') if row.labelPos == 'left' or string.find(row.labelPos,'west') then table.insert(result,'top: '..tostring(row.dy+row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+3)..'px; text-align: left; width:'..tostring(w)..'px;') elseif row.labelPos == 'right' or string.find(row.labelPos,'east') then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+w-3)..'px; text-align: right; width: max-content; transform: translateX(-100%);') elseif row.labelPos == 'top' or row.labelPos== 'north' then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') elseif row.labelPos == 'bottom' or row.labelPos=='south' then local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*1.1 table.insert(result,'top: '..tostring(row.gridy+w-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') else -- center or centre local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*0.6 table.insert(result,'top: '..tostring(row.gridy+(h/2)-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') end if shape.textSP and shape.textSP ~='0px' then table.insert(result,"letter-spacing: "..shape.textSP..';') end if shape.textLH and shape.textLH ~='120%' then table.insert(result,"line-height: "..shape.textLH..';') end table.insert(result,"vertical-align: middle;\">"..row.labelText.."</div>") end local function makeTextItem(result, row, shape, align, tlink, textItem, dotItem) local w,h,r=getsize(shape.shapeSize) table.insert(result,"<div ") if row.title ~= "" then table.insert(result," title=\""..row.title.."\" ") end local ty,bry,linkoffset = 0,0,0 local compy=0 local lh=tonumber(string.match(shape.textLH or '120',"%d+"))/100 if dotItem==1 or (dotItem==2 and row.posType~='n-line') then -- if there is a dotTag in the middle of the shape then use the tag settings ty=tonumber(string.match(shape.tagSize,"%d+")) or 0 table.insert(result,"style=\"position:absolute; line-height: 120%; top: "..tostring(row.gridy-ty*0.62)) table.insert(result,"px; left: "..tostring(row.gridx).."px; width: fit-content; ") table.insert(result,"text-align: center; color: "..getColor(shape.tagColor).."; background-color: transparent;") local trf="" if shape.tagAngle ~="0deg" then trf=" rotate("..shape.tagAngle..")" end table.insert(result, "transform: translateX(-50%)"..trf.."; font-size: "..shape.tagSize..";") if shape.tagSpacer~='0px' then table.insert(result, "letter-spacing:"..shape.tagSpacer..";") end else -- or add tfx settings for left, right or center align, colors, backgrounds, border-outline table.insert(result,'style="position:absolute; ') if dotItem==2 then -- dotTag is out at x,y so 85% ty=tonumber(string.match(shape.tagSize or '0',"%d+")) table.insert(result,'font-size: '..shape.tagSize..'; padding:0px 2px;line-height: 85%; top: '..tostring(row.dy+row.gridy-ty*0.52)) else -- it is labelText, so use textLH or 120% ty=tonumber(string.match(shape.textSZ or '11',"%d+")) if row.labelPos=='northwest' or row.labelPos=='northeast' then compy=-ty elseif row.labelPos=='southeast' or row.labelPos=='southwest' then compy=ty/2 end if row.labelPos and not (row.labelPos== 'auto' or row.labelPos=='') then bry=(select(2, string.gsub(row.labelText,"<br>", ""))*lh) -- is it a multiline text? expand by line-height /120%? if row.posType == 'with-line' then bry=0 end if row.labelPos=='bottom' or row.labelPos == 'south' then bry = 0 -- and shift by none, all or half elseif row.labelPos=='top' or row.labelPos == 'north' then if shape.shape=='image:' then bry= 1 + math.min(w/2-10,0)-bry*ty else bry= -bry*ty+2 end else bry=-bry*(ty/2 * lh) end end if row.posType == 'photo-panel' then bry=bry+3 end table.insert(result,'font-size: '..shape.textSZ..'; padding:0px 3px;line-height: '..shape.textLH..'; ') table.insert(result,'top: '..tostring(row.dy+row.gridy+compy+bry-ty*lh/2)) end table.insert(result,"px; left: "..tostring(row.dx+row.gridx).."px; color: "..getColor(shape.textCL).."; ") table.insert(result,"width: max-content; ") local trf="" if shape.textNG ~="0deg" then trf="rotate("..shape.textNG..")" end if shape.textOL~="0px" then table.insert(result,"background-color: "..getColor(shape.textBG).."; ") table.insert(result,"border: "..shape.textOL.." solid "..getColor(shape.textCL).."; border-radius:6px;") else table.insert(result,"background-color: transparent;") end if row.labelPos=="right" or string.find(row.labelPos,'east') then table.insert(result,"text-align: left; ") linkoffset=w if shape.textNG ~="0px" then table.insert(result,"transform-origin: left; transform: rotate("..shape.textNG.."); ") end elseif row.labelPos=="left" or string.find(row.labelPos,'west') then if shape.textNG ~="0px" then table.insert(result,"transform-origin: right;") end linkoffset=-w table.insert(result,"text-align: right; transform: translateX(-100%) "..trf.."; ") else table.insert(result,"text-align: center; transform: translateX(-50%) "..trf.."; ") end table.insert(result,'font-weight: normal; line-height: '..shape.textLH..'; letter-spacing:'..shape.textSP..'; vertical-align: bottom;') end if string.find(shape.textAT or '','bold') then textItem='<b>'..textItem..'</b>' end if string.find(shape.textAT or '','italic') then textItem='<i>'..textItem..'</i>' end if shape.textOL=='0px' and shape.textBG~='transparent' and dotItem==0 then table.insert(result,'\"><span style=\"background-color: '..getColor(shape.textBG)..'; color:inherit;\">'..textItem..'</span></div>') else table.insert(result,"\">"..textItem.."</div>") end if tlink~='' and not string.match(row.param1 or "","nolink") then table.insert(result,makeLinkBox(row.gridx+row.dx+linkoffset, row.gridy+row.dy+bry, 16, row.dotTag..' '..row.title,tlink)) end end local function getshapetable(row,shape) -- Construct CSS divs for a dot from shape and map data local result={} local w,h,r=getsize(shape.shapeSize) local tshape,tlink=checkfortooltip(row.title,row.dx,row.dy,row.dotTag,row.dlink,string.match(row.param1 or "","nolink") ) local align=row.labelPos or '' local offsetx,offsety=0,0 local ty=tonumber(string.match((shape.tagSize or 9),"%d+" )) if row.labelText and row.labelText~='' then ty=tonumber(string.match((shape.textSZ or 11),"%d+" )) else align='center' end -- it is just for dotTag, so justify center -- identify align value and extend offsets local widthzone,heightzone = w/2+4,h/2+4 local theta,r = math.deg( math.atan2(row.dy, row.dx)) , math.sqrt(row.dx^2 + row.dy^2) if align=='auto' or align=='' then if (theta < -112 or theta > 112) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "left" offsetx=1 elseif (theta > -68 and theta < 68) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "right" offsetx=-1 elseif theta <0 then offsety=ty/2-1 -- bottom else offsety=0-ty/2+1 -- top end elseif align=='left' or string.find(align,'west') then row.dx=row.dx - w/2 widthzone=4 offsetx=-1 elseif align=='right' or string.find(align,'east') then row.dx=row.dx + w/2 widthzone=4 offsetx=1 offsety=-ty/2 +1 elseif align=='top' or align=='north' then if string.find(shape.shape,'curve') then row.dy=row.dy-(h/2)-12 heightzone=4 offsety=ty/2-1 else row.dy=row.dy - h-2 heightzone=4 offsety=ty/2-1 end elseif align=='bottom' or align=='south' then if string.find(shape.shape,'curve') then row.dy=row.dy+(h/2) heightzone=4 offsety=ty/2-1 else row.dy=row.dy + h+2 heightzone=4 offsety=0-ty/2-1 end end if r > widthzone and shape.textEW ~= "0px" and not string.match(row.param1 or "","noline") then makeLineTo(result, row.gridx+1, row.gridy-1, row.gridx+row.dx+offsetx, row.gridy+row.dy+offsety, shape.textEW, shape.textES,shape.textEC,1) end if row.posType and row.posType=='mark-line' then if row.gridx2 and row.gridy2 then makeLineTo(result, row.gridx, row.gridy, row.gridx2, row.gridy2, tostring(row.mlWidth)..'px', row.mlStyle, getColor(shape.textEC),row.mlGap) -- debugmsg('makeLineTo line drawn from '..row.code..' with width '..tostring(row.mlWidth)..'px '..row.mlStyle..' and color '..getColor(shape.textEC)) end end if w ~= 0 then if shape.shape=='itriangle' then shape.shape='triangle' shape.shapeSize=tostring(w)..'px'..tostring(w/2)..'px' end if shape.shape=="triangle" then if shape.outlineWidth ~= "0px" then makeTriangle(result,row,shape,true,false) -- larger triangle to give the outline, if required end makeTriangle(result,row,shape,false,tshape) -- smaller triangle to fit over the top elseif shape.shape=="square" or shape.shape=="box" or shape.shape=='panel' then makeSquare(result,row,shape,tshape) elseif shape.shape=="circle" or shape.shape=="ellipse" then makeCircle(result,row,shape,tshape) elseif string.find(shape.shape,'image:')==1 then makeImage(result,row,shape) elseif shape.shape=="rulea" or shape.shape=='rule' then makeRuleA(result,row,shape) elseif shape.shape=="curvea" or shape.shape=="curvec" then makeCurveA(result,row,shape) else -- use a pathshape clipPath if shape.outlineWidth ~= "0px" then makeClipPath(result,row,shape,true,false) -- larger path-shape to give the outline, if required end makeClipPath(result,row,shape,false,tshape) end end if row.ppwidth and row.ppwidth>0 then makePhotoPanel(result,row,shape) end if shape.shape=='panel' and row.labelText then makePanelText(result, row, shape) else if row.dotTag and row.dotTag ~= "" then -- there is a dotTag if (row.dx==0 and row.dy==0) and w>0 then -- it is on the dot so if dotsize is not 0 any label is ignored makeTextItem(result, row, shape, align, tlink, '<b>'..row.dotTag..'</b>', 1) else if row.labelText and row.labelText~='' then -- tag and label both used makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.labelText, 0) else makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.dotTag, 2) -- tag is ouside the dot end end else if (row.labelText and row.labelText~='') then -- just the label. No tag makeTextItem(result, row, shape, align, tlink, row.labelText, 0) end end end if tlink and tlink~='' then table.insert(result,makeLinkBox(row.gridx, row.gridy,w+3,row.dotTag..' '..row.title,tlink)) end return table.concat(result) end local function getmapframecontent(args,use) local result, comma= {}, '' local propertyTable= {} local color=splitItem(string.lower( args['map-data-color'] or 'dark orange'), 2) if use == 'basemap' then table.insert(result, '[') propertyTable.stroke = "#000000" propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') propertyTable['stroke-opacity'] = tonumber(color[2] or '15')/100 else propertyTable.title = args['map-data-text'] or args['map-data'] or '' propertyTable.stroke = getColor(color[1]) propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') propertyTable['stroke-opacity'] = tonumber(color[2] or '100')/100 end if args['map-data-inverse'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geomask', ids = args['map-data-inverse'], properties = {title = (args['map-data-text'] or ''), stroke = '#555555', fill = '#555555', ['fill-opacity'] = 0.1, ['stroke-width'] = 1, ['stroke-opacity'] = 0.5 } } table.insert(result, mapJson) comma=', ' end if args['map-data-heavy'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-heavy'], properties = { title = args['map-data-heavy'], stroke = propertyTable.stroke, ['stroke-width'] = 9, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.25 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data-light'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-light'], properties = { title = args['map-data-light'], stroke = propertyTable.stroke, ['stroke-width'] = 3, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.25 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data'] then propertyTable['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.1 local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data'], properties = propertyTable } table.insert(result, comma .. mapJson) comma=', ' end if args['map-wdqs'] then -- inserts a SPARQL wdqs query to find geopoints, geoshapes etc from wikidata local mapSparql = mw.text.jsonEncode{ type = 'ExternalData', service = args['map-wdqs-type'] or 'geopoint', query = args['map-wdqs'], } table.insert(result, comma .. mapSparql) -- debugmsg('mapSparql = ' .. mapSparql) end if args['map-raw'] then table.insert(result, comma .. args['map-raw']) end if use=='basemap' then table.insert(result,']') end return table.concat(result) end --eg | minilocator=filename,bottom right,132px153px, 38%,60%, 22px local function makeLocatorMap (args, result) local miniFile,pos,itemlist,miniW,miniH, miniX,miniY,miniBox, miniBH if args['mini-locator'] then pos,miniFile=extractItem(args['mini-locator']) -- first item is filename, in quotes if it includes commas itemlist=splitItem(pos,5) -- put items in a table filename removed, position,WpxHpx, x%y%,box pos=itemlist[2] or 'right' miniW,miniH=getsize(itemlist[3]) miniX=tonumber(string.match(itemlist[4] or '0','[%d]+'))*miniW/100 miniY=tonumber(string.match(itemlist[5] or '0','[%d]+'))*miniH/100 miniBox=tonumber(string.match(itemlist[6] or '0','[%d]+')) miniBox=miniBox*miniW/100 miniBH=miniBox * maplist.height/maplist.width elseif args['mini-file'] then miniFile = args['mini-file'] pos=string.lower(args.minimap or 'right') if pos=='file' then pos='right' end miniW,miniH = tonumber(args['mini-width'] or 60), tonumber(args['mini-height'] or 60) -- find top left corner of locator miniBox,miniBH=tonumber(args['minimap-boxwidth'] or '0'),0 -- firm up pos offsets for dot (with % or not) and boxsize if any, miniX, miniY=args['minipog-gx'],args['minipog-gy'] if not miniX then miniX=tonumber(args['minipog-x'] or '0') else miniX=tonumber(miniX)*tonumber(miniW)/100 end if not miniY then miniY=tonumber(args['minipog-y'] or '0') else miniY=tonumber(miniY)*tonumber(miniH)/100 end if args['minipog-gx'] then miniBox=miniBox*miniW/100 end miniBH=miniBox * maplist.height/maplist.width else return end local miniTop,miniLeft=0,1 if not string.find(pos,'top') then --only use top left, as link box is in top right or put bottom left or right miniTop=maplist.height+2-miniH if string.find(pos,'right') then miniTop=miniTop-15 -- to avoid (c) line miniLeft=maplist.width-1-miniW end end table.insert(result,'<div style="position: absolute; outline-width: 1px; outline-style: solid; outline-color: white;') table.insert(result,'top: '..tostring(miniTop)..'px; left:'..tostring(miniLeft)..'px; width:'..tostring(miniW)..'px;' ) table.insert(result,'background-color:transparent; color:inherit;">[[File:'..miniFile..'|'..tostring(miniW)..'px|File:'..miniFile..']]</div>') if miniX and miniX>0 then if args['minipog-y'] then miniY=miniY/1.04 end if args['minipog-x'] then miniX=miniX/1.04 end if miniBox<1 then table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-3-6)..'px; left:'..tostring(miniX+miniLeft-3)..'px;') table.insert(result, 'width: 6px; background-color:transparent; color:inherit;">[[File:Red pog.svg|6px|link=]]</div>') else table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-miniBH/2)..'px; left:'..tostring(miniX+miniLeft-miniBox/2)..'px;') table.insert(result, 'width: '..tostring(miniBox)..'px; height:'..tostring(miniBH)..'px; outline:1px solid #AA1205; background-color:#AA120522; color:inherit;"></div>') end end end local function makeArcText(args,result,nval) local items, itemlist='',{} local arcText='' if args['arc'..nval] then items=convertCoords (args['arc'..nval]) items,arcText=extractItem(items) -- first item is text, in quotes if it includes commas itemlist=splitItem(items,9) -- put items in a table: text, lat,lon,size,color,angle,gap,radius,ellipse if itemlist[4]=='' then itemlist[4]='12' end itemlist[4]=string.match((itemlist[4] or '12'),"[%.%-?%d]+") if itemlist[5]=='' then itemlist[5]='grey' end itemlist[6]=string.match((itemlist[6] or '0'),"[%.%-?%d]+") itemlist[7]=string.match((itemlist[7] or '0'),"[%.%-?%d]+") itemlist[8]=string.match((itemlist[8] or '0'),"[%.%-?%d]+") end if args['arc-coord'..nval] then local itemTab=splitItem(convertCoordsTrad (args['arc-coord'..nval]),2) maplist.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) maplist.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) else maplist.lat=tonumber(string.match(args['arc-lat'..nval] or itemlist[2] or '0',"[%.%-?%d]+")) maplist.lon=tonumber(string.match(args['arc-lon'..nval] or itemlist[3] or '0',"[%.%-?%d]+")) end local arcX,arcY=maptogrid(maplist,6) arcText = args['arc-text'..nval] or arcText local fontSize =tonumber(args['arc-text-size'..nval] or itemlist[4] or '12') local textColor=getColor(string.gsub(args['arc-text-color'..nval] or itemlist[5] or 'grey','[%s]+','') ) local arcAngle= tonumber((args['arc-angle'..nval]) or (itemlist[6]) or '45')-90 local arcRadius =tonumber(args['arc-radius'..nval] or itemlist[8] or '0.05') local arcGap = tonumber(args['arc-gap'..nval] or itemlist[7] or '1')* ( ( math.sin(8-math.rad(arcRadius))^8 )+0.4 )*( ( fontSize+6 )/15 ) arcRadius=450*arcRadius*0.75 local ellipseFactor=tonumber(args['ellipse-factor'..nval] or itemlist[9] or '1') local arcRotate =arcAngle+90 if arcGap<0 then arcRotate=arcAngle-90 end local latF=arcY - fontSize + (0-(math.sin(math.rad(arcAngle))) * arcRadius) local lonF=arcX - fontSize +(0-(math.cos(math.rad(arcAngle))) * arcRadius) local step=1 for codepoint in mw.ustring.gcodepoint( arcText ) do -- block step=1,#arcText do table.insert(result,'<div style="position: absolute;') local posY=tostring(round( (latF + (math.sin(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius)) *ellipseFactor,2))..'px;' local posX=tostring(round( (lonF + (math.cos(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius))/ellipseFactor,2))..'px;' table.insert(result,' top: '..posY..' left: '..posX..' transform: rotate( '..tostring(round(arcRotate +((step-1)*arcGap)),2)..'deg);') table.insert(result,'width:'..tostring(fontSize*2)..'px; text-align: center; background-color:transparent; color: '..textColor..';') table.insert(result,'vertical-align: baseline; font-size: '..tostring(fontSize)..'px;">'..mw.ustring.char(codepoint)..'</div>') step=step+1 end end local function makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) local item={} itemdescription=stripdivs(itemdescription or '') local templon=lon if lon > 180 then templon=lon-360 end --for hemisphere+ or -1 dots if lon < -180 then templon=lon+360 end -- use 'real' coordinates for geohack label, while retaining shifted coords for plot if itemcolor=='transparent' then itemcolor='white' end itemcolor=getColor(itemcolor) -- ensure no opacity, which breaks maplink if string.find(itemcolor,'#')==1 and #itemcolor>7 then itemcolor=string.sub(itemcolor,1,7) end table.insert(item, '{ "type": "Feature", "properties": {') table.insert(item, ' "title": "'..itemtitle..'",') table.insert(item, ' "description": "'..itemdescription) table.insert(item, ' ([https://geohack.toolforge.org/geohack.php?params='..tostring(lat)..';'..tostring(templon)) table.insert(item, '_dim:2000 '..tostring(lat)..','..tostring(templon)..'])",') table.insert(item, ' "marker-symbol": "-number-'..string.gsub(group,'%W','')..'", "marker-size": "medium", "marker-color": "'..itemcolor..'" },') table.insert(item, ' "geometry": {"type": "Point", "coordinates": ['..tostring(lon)..','..tostring(lat)..'] } }') return table.concat(item) end local function makeLegendBox(result,args) local legend ={} local line,count, maxWidth='',1,8 local item line,item=extractItem(args.legendBox or '') local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end legend.Text = a -- convert hats to line breaks line=splitItem(line,6) -- (text, size, poition,background color, text/outline color, param options) legend.Size=line[2] or '150px80px1px' legend.Pos=line[3] or '10px10px' legend.Background=line[4] or 'beigeground' legend.Color=line[5] or 'darkbrown' legend.Param= line[6] or '' local argnum,legendCount,titleHeight='1',1,0 if (legend.Text and legend.Text~='') then titleHeight=15+(13.4*(select(2, string.gsub(legend.Text,"<br>", "")))) end local legendLine,legendGroup,legendY={},{},{} while args['legendItem'..argnum] do -- assign legendLine, legendGroup, legendY for each dot line,legendLine[legendCount] = extractItem(args['legendItem'..argnum] or '') line=splitItem(line,3) legendGroup[legendCount]=line[2] or argnum if shapeList[legendGroup[legendCount]] then maxWidth=math.max(tonumber(string.match(shapeList[legendGroup[legendCount]].shapeSize or '10','[%d]+')),maxWidth) else maxWidth=math.max(tonumber(string.match(shapeList['1'].shapeSize or '10','[%d]+')),maxWidth) end maxWidth=maxWidth+1 if line[3] then legendY[legendCount]=tonumber(string.match(line[3],'[%d]+')) else legendY[legendCount] = 3+maxWidth*(legendCount-1)+titleHeight -- if (legend.Text and legend.Text~='') then legendY[legendCount]=legendY[legendCount]+15 end end legendCount=legendCount+1 argnum=tostring(legendCount) end local w,h,r=getsize(legend.Size or '') local x,y=getsize(legend.Pos or '') local div=mw.html.create ('div') div:css('position', 'absolute') div:css('outline', '1px solid'..getColor(legend.Color)) if r~=0 then div:css('border-radius',tostring(r).."px") end div :css('top', y.."px") :css('left', x.."px") :css('width', w.."px") :css('height', h.."px") :css('line-height','105%') :css('background-color', getColor(legend.Background) ) :css('color','inherit') if not string.find(legend.Param,'noshadow') then div:css('box-shadow', '2px 2px 4px #33203335') end div:tag( 'div' ) :css('position', 'absolute') :css('top','1px') :css('left', (w/2).."px") :css('width',(w-8)..'px') :css('text-align', 'center') :css('color', getColor(legend.Color)) :css('transform', 'translateX(-50%)') :css('font-size','11px') :wikitext(legend.Text) :done() for lct=1,legendCount-1 do --local t=legendGroup[lct] local shape=shapeList[legendGroup[lct]] or shapeList['1'] local row={} row.gridx=3+maxWidth/2 row.gridy=(legendY[lct] or 0) + 5 if shape.shape=='image:' then row.gridy=row.gridy+6 end row.dx=0 row.dy=0 local legendShape= getshapetable(row,shape) div:wikitext(legendShape) :tag( 'div' ) :css('position', 'absolute') :css('top',(legendY[lct] or 0)..'px') :css('left', (maxWidth+6)..'px') :css('width', (w-maxWidth-6)..'px') :css('text-align', 'left') :css('line-height','103%') :css('color', getColor(legend.Color)) :css('font-size','10px') :wikitext(legendLine[lct]) :done() end div:allDone() table.insert(result,tostring(div)) end function p._main ( args ) local result={} local frame=mw.getCurrentFrame() local dotTable={} local magVal,scaleVal = args.magnify,'' local origH,origW=maplist.height,maplist.width if magVal then --set up the values needed to magnify the top-right portion of the map magVal=tonumber(string.match(magVal or '0',"[%.%-?%d]+")) or 1 if magVal>1 and magVal <=2 then maplist.height= round(maplist.height/magVal,0) maplist.width=round(maplist.width/magVal,0) scaleVal='transform: scale('..magVal..') translateY('..tostring((origH-maplist.height)/2)..'px);' else magVal=1 end --debugmsg('magVal='..magVal..', Magnify: H='..maplist.height..', ('..origH..'), W='..maplist.width..', ('..origW..'), scale='..scaleVal) --debugmsg('top: '..tostring(((origW-maplist.width))/2)..'px;right: '..tostring(0-((origW-maplist.width))/2)..'px; (alt top: '..tostring(((origH-maplist.height))/2)..'px;)') end -- set up the three nested div boxes (plus an extra if centered) to put the map plus title/caption area, in an appropriate frame on the page if args.float=='center' or args.float=='centre' then table.insert(result,'<div class="center"><div class="thumb tnone">') elseif args.float=='left' then table.insert(result,'<div class="thumb tleft">') else table.insert(result,'<div class="thumb tright">') end table.insert(result,'<div class="thumbinner" style="position: relative; top: 0px; right: 0px; width: '..(args.width or "400")..'px;">') if args.title then table.insert(result,'<div class="center" style="font-weight:bold">'..args.title..'</div>') end if magVal and magVal >1 and magVal<=2 then table.insert(result,'<div class="thumbinner noresize" style="display:block; position: relative; outline:0px; border:0px; padding:0px; background-color:transparent; color:inherit;') table.insert(result,'top: 50%; right: '..tostring(0-((origW-maplist.width))/2)..'px; ') table.insert(result,'height: '..origH..'px; width: '..maplist.width..'px; '..scaleVal..'">') else magVal=1 table.insert(result,'<div class="thumbinner noresize" style="position: relative; outline:0px; border:0px; padding:0px;') table.insert(result,'top: 0px; right: 0px; ') table.insert(result,'height: '..maplist.height..'px; width: '..maplist.width..'px ">') end -- Create the basemap using mapframe local mapframecontent=getmapframecontent(args,'basemap') table.insert(result, frame:extensionTag{ name ='mapframe', content=mapframecontent, args={width=tostring(maplist.width), height=tostring(maplist.height), zoom=tostring(maplist.zoom), longitude=tostring(maplist.lonbase), latitude=tostring(maplist.latbase), mapstyle=maplist.mapstyle, frameless=true } } ) --Add coverall box to block the unhelpful links from mapframe - which wouldn't include all the dots. Reinstate some links to osm and wikimedia table.insert(result,'<div style="position: absolute;width:'..tostring(maplist.width)..'px; height:'..tostring(maplist.height)..'px;') table.insert(result,'top:0px;left:0px;background-color:#FFFFFF00; color:inherit;"></div>') --Add replacent hover-links for OpenStreetMap and maps terms and conditions table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-13)..'px; width: 12px; height: 12px">') table.insert(result,'[[file:Transparent.svg|12px|link=https://www.openstreetmap.org/copyright|'..aboutOSM..']]</div>') table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-110)..'px;') table.insert(result,'width: 12px; height: 12px background-color: transparent; color:inherit;">') table.insert(result,'[[file:Transparent.svg|12px| link=https://foundation.wikimedia.org/wiki/Policy:Maps_Terms_of_Use|'..termsOfUse..']]</div>') -- Add scale-line if not args.scalemark or args.scalemark~='0' then local top=maplist.height-42 local left=maplist.width-61-(tonumber(args.scalemark or '1')) local minipos=string.lower(args.minimap or '') -- scalemark gets pushed left if it would be behind the minimap if minipos~='' and not(string.find(minipos,'left') or string.find(minipos,'top') ) then local offset=tonumber(args.scalemark or '1')-tonumber(args['mini-width'] or '60') if offset<1 then left=maplist.width-61-tonumber(args['mini-width'] or '60') end end if maplist.width-left >216 then top=top+14 end -- shunt scaleline down if it is beyond the copyright stuff local scalek,scalem=getScale(maplist.zoom, maplist.latbase, magVal) local magReduce='' table.insert(result,"<div style=\"display:inline-block; position: absolute; background-color: #111111") table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;M0,8 l0,4 l20,0 l0,-4 l-0.3,0 l0,3.7 l-19.4,0 l0,-3.7 z&#39;) ") table.insert(result,"; width:20px") -- path is a 20px20px box, and then rescales if magVal==1 then table.insert(result,"; top:"..tostring(top-1).."px") table.insert(result,"; left:"..tostring(left+16).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5)..", "..tostring(1.5)..")") else -- shrink the scalemark to compensate for magnification table.insert(result,"; top:"..tostring(top-(magVal*0.28)).."px") table.insert(result,"; left:"..tostring(left+(16*(magVal*1.15))).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5*(1/magVal))..", "..tostring(1.5*(1/magVal))..")") magReduce= 'scale('..tostring(1/magVal)..')' end table.insert(result,"\"></div>") table.insert(result,'<div style="position: absolute; top: '..tostring(top)..'px; left: '..tostring(left+47)..'px; font-size: 9.5px; line-height: 126%; width: fit-content;') table.insert(result,'color: #444433; background-color: transparent; text-align: right; transform: '..magReduce..' translateX(-100%);">'..scalek..'<br>'..scalem..'</div>') end --Set up the shapeList and dotList tables, to provide data to go on the map local sgNumbers,sgSortable={},{} --s1,s2 sgNumbers["1"]="1" if args.useFormatStyle and args.useFormatStyle=='shortstyle' then shapeList=ParseShapeTypes (shapeList,args,"1") for argindex,argv in pairs(args) do -- build a list of all the numbered sg's that have been used if string.find(argindex,"sg[a-f,n%d]+") == 1 then -- only look through the sga,sgb, sgc,sgd,sge,sgf and sgn args local x=string.match(argindex,"[%d]+") -- find its number if x and not sgNumbers[x] then sgNumbers[x]=x end -- only add if not already found end if string.find(argindex,"sg[a-f]H") == 1 and highlightNum then highlightOption=true end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form for k,v in pairs(sgSortable) do -- work through the sorted list, parsing each set of sg's in turn, from 1 upwards shapeList=ParseShapeTypes (shapeList,args,v) end if highlightOption==true then shapeList=ParseShapeTypes (shapeList,args,'H') end end local dotList,dotresult,dotItemTable,dotGroupList={},{},{},{} if (not args.useFormatStyle) or args.useFormatStyle=='standardstyle' then local dotmax=0 for indx in pairs(args) do if string.match(indx,'mark%-coord[%d]+') or string.match(indx,'lat[%d]+') then dotmax=math.max(dotmax, tonumber(string.match(indx,"[%d]+"))) end if (indx=='shapeH') or (indx=='shape-colorH') or (indx=='shape-outlineH') or (indx=='label-colorH') then highlightOption=true end end dotItemTable=tradstyleParseShapes(args,dotItemTable,dotmax) for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) then sgNumbers[x]=x -- add it to the list end end else for indx in pairs(args) do if string.match(indx,'dot[%d]+') then table.insert(dotList,indx) end --add the index name for dot1, dot2 etc to dotList end table.sort(dotList,morethan) for indx,dotName in pairs(dotList) do dotresult=ParseData(args,string.match(dotName,'[%d]+') ) -- using each dot number, assign the settings for each dot to a dotresult item line table.insert(dotItemTable,dotresult) -- and store that item line within the dotItemTable end end for arcVal = 65,91 do -- check through args looking for any arcs local arcLetter=string.char(arcVal) if args['arc-text'..arcLetter] or args['arc'..arcLetter] then makeArcText(args,result,arcLetter) end end local dotdivs='' local ddots=0 if dotItemTable[1] then local ddots=(dotItemTable[1].lat or 0)+(dotItemTable[1].lon or 0) end local fgroup='F'..tostring(maplist.latbase+maplist.lonbase+ddots ) local FullscreenList={} local addcomma='' for i,dotitem in pairs(dotItemTable) do -- working throug each dot item, merge the dot and shape values into a full set of css text local dotgroup= dotitem.group or "0" if dotitem.posType=='mark-line' and dotitem.markDest then --find destination xy values for any mark-lines for n,v in pairs(dotItemTable) do if v.code == dotitem.markDest then dotitem.gridx2=v.gridx dotitem.gridy2=v.gridy break end end end local qtype=dotitem.group -- find which shape group each dot has been assigned --debugmsg('dotgroup='..qtype..', sg='..(sgNumbers[qtype] or 'nil')..' , shapeList='..shapeList[qtype].shape) if not sgNumbers[qtype] then qtype="0" end --shapeList[dotitem.group] will give access to the shape values for that dot if highlightNum==dotitem.code and highlightOption==true and shapeList['H'] then table.insert(result, getshapetable(dotitem,shapeList['H'])) else table.insert(result, getshapetable(dotitem,shapeList[qtype])) -- Add the actual css instructions for each dot end if shapeList[dotgroup] and not string.find((dotitem.param1 or ''),'nomap') then -- only add if not excluded with 'nomap' labelText local ftext='' if dotitem.dotTag~='' and not string.match(dotitem.labelText or '','[%d]') then ftext=stripdivs(dotitem.dotTag or '')..' <br>' end if (dotitem.labelText ~= ftext) and dotitem.dotLink =='' then ftext=ftext..' '..stripdivs(dotitem.labelText)..'<br>' end if (dotitem.dotLink) and (dotitem.dotLink ~='') then ftext=ftext..dotitem.dotLink..'<br>' end if dotitem.imageName then ftext=ftext..'[[File:'..dotitem.imageName..'|250px]]' end table.insert(FullscreenList,1, makeFullscreenItem (string.gsub(ftext,"[\n]+"," "), dotitem.info,dotitem.lat,dotitem.lon,fgroup,shapeList[dotgroup].shapeColor)..addcomma ) addcomma=', ' end -- makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) -- Always add to start of list, to reverse the sequence, and separate with commas except for first item, which is now at the end end if args.legendBox then makeLegendBox(result,args) end if args.minimap or args['mini-locator'] then makeLocatorMap(args,result) end -- add tag link and details for fullscreen version addcomma='' if (mapframecontent or '[]') ~= '[]' then addcomma=',' end mapframecontent=getmapframecontent(args,'fullscreen') local contentstart='[ '..mapframecontent..addcomma..'{ "type": "FeatureCollection", "features": [ ' --extra features after first square bracket local contentend=' ] } ]' table.insert(result, '<div style="position: absolute;top: 9px;left: '..tostring(maplist.width-34)..'px">') table.insert(result, '<div style="color: white; opacity:100; font-size: 19px; font-weight:normal; text-align: left;">') table.insert(result, frame:extensionTag{ name ='maplink', content=contentstart..table.concat(FullscreenList)..contentend, args={zoom=tostring(maplist.zoom+1), class='no-icon', frameless='1', latitude=tostring(maplist.latbase), longitude=tostring(maplist.lonbase), --add invisble 'en-spaces' for tooltip text='<div title="'..fullscreenlinktext..'">&nbsp;&nbsp;&nbsp;</div>'} } ) table.insert(result,'</div></div>') -- end of maplink ----- -- add closing div for main map table.insert(result,'</div>') -- collate caption material to go in the outer div class local autocaption=string.lower(args['auto-caption'] or 'no') local autoOff=autocaption:match("(%w+)(.*)") -- select the first word in autocaption and see if it is a negativeAnswer) if args.caption or not negativeAnswer[autoOff] then table.insert(result,'<div class="thumbcaption" style="text-align:left">') if args.caption then table.insert(result,args.caption) end end local columns=tonumber(autoOff:match("[%d]+") or '1') if columns>1 then columns=round(maplist.width/(columns*17), 0) end -- convert from em to px for historical reasons --for k in pairs(dotList) do capchk=capchk..(args["dotlink"..k] or '') end local capchk=nil local captionList = {} for key, value in pairs(dotItemTable) do -- only add an autocaption line if there is both a dotTag and a dotLink line available and it is not marked as nolist if value.dotTag and value.dotTag~='' and (not string.find(value.param1 or '','nolist')) and string.gsub(value.dotLink or '',"%s+","")~='' then table.insert(captionList, {key = key, value = value}) capchk=true end end if capchk and (not negativeAnswer[autoOff]) then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) local myDivision = string.gsub((args.toggletext or toggletext), "%s+", "") if string.find(autocaption,'collaps') then table.insert(result,'<hr><div class="mw-customtoggle-'..myDivision..'">'..(args.toggletext or toggletext)..'</div>') if string.find(autocaption,'collapsed') then table.insert(result,'<div class="mw-collapsible mw-collapsed" id="mw-customcollapsible-'..myDivision..'">') else table.insert(result,'<div class="mw-collapsible" id="mw-customcollapsible-'..myDivision..'">') end end if string.find(autocaption, 'columns=') then columns=string.match(autocaption,'[%d]+',string.find(autocaption, 'columns=') ) end table.insert(result,'<div class="mw-collapsible-content" style="column-count:'..columns..'; column-rule:solid 1px;text-align:left;padding-top:5px">') table.sort(dotList,lessthan) local nval,ngrp='','0' for k,v in pairs(captionList) do nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag ngrp=v.value.group or '0' if v.value.dotLink and v.value.dotLink~='' and nval and nval~='' then local c1,c2 if nval==args.highlight then c1,c2=checkColors(shapeList['H'].shapeColor) else c1,c2=checkColors(shapeList[ngrp].shapeColor) end table.insert(result,'<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;') table.insert(result,'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div> '..v.value.dotLink..'<br>') end end table.insert(result,'</div>') -- end for caption-content div if string.find(autocaption,'collaps') then table.insert(result,'</div>') end -- end for toggle frame end if args.caption or not negativeAnswer[autoOff] then table.insert(result,'</div>') end -- end for whole caption frame table.insert(result,'</div></div>') -- outer two frames if args.float == 'center' or args.float=='centre' then table.insert(result,'</div>') end if args['show-new-format'] == 'hints' then -- provide a 'format hint panel' in the 'Preview Box' local w="<small>Below are some template hints for the 'sga' compressed version, " w=w..'{{tl|OSM Location dots}}. It can use these, instead of the more verbose {{tl|OSM Location map}} parameter format. ' w=w..'Data is divided between a "ShapeGroup" and the "Dots", so that a single shapeGroup can be used for multiple dots on the map. ' previewMsg(w..'(nb. the "group" value can be the number or an assigned name of a shapeGroup)</small>') w='<small>{{tl|OSM Location dots}}: | dot(n)=group,lat,lon,dotTag | ' w=w..'dotlink=link/tooltip | dotlabel=label,position,dx,dy,param1,info | dotpic=filename <br>' w=w.."(nb. param1 options include 'nolink' 'nolist' 'nomap' 'hemisphere-1' 'hemisphere+1', 'noline' - quotes not required, separate with spaces).<br>" w=w..'| sga = Shape,Sizepx,Color,Angledeg | sgb= OutlineWidth,Color,Style | sgc=TextSize,Color,Angle,bold italic <br>' w=w..'| sgd=TagSizepx,Color,Spacer,Angledeg | sge=LineWidth,Color,Style | sgf=TextSpacingpx,LineHeight%,Outlinepx,backgroundColor<br>' w=w..'| sgn=Name (optional, to assign a meaningful name) | sgp=Parent (can be the name or number of the parent shapeGroup. ' previewMsg(w..'Each shapegroup will inherit values from a parent, stretching back to "sga1" and its default values.)</small>') if #pmsg then local dbg={} for i,x in pairs(pmsg) do table.insert(dbg,x..'<br>') end dbg=mw.addWarning(table.concat(dbg)) table.insert(result, dbg) end end if args.coordtest then debugmsg(mw.text.nowiki(args.coordtest)) end for i,x in pairs(msg) do table.insert(result, x..'<br>') end return table.concat(result) end function p.main(frame) local args = getArgs(frame) local itemTab={} maplist.width=tonumber(args.width) or 400 maplist.height=tonumber(args.height) or 300 if args.coord then itemTab=splitItem(convertCoordsTrad (args.coord),2) maplist.latbase=itemTab[1] maplist.lonbase=itemTab[2] else maplist.lonbase=tonumber(args.lon) or 5 maplist.latbase=tonumber(args.lat) or 0 end maplist.zoom=tonumber(args.zoom) or 1 visibleLinks=args.showlinks highlightNum=args.highlight if args.nolabels=='1' then maplist.mapstyle='osm' else maplist.mapstyle='osm-intl' end return p._main(args) end return p jtp84rcxtfdtxa8hjzphw3mtyy34vr6 204463 204462 2026-07-05T15:20:11Z HaxelKaszëba 17492 204463 Scribunto text/plain require('strict') local delink=require('Module:Delink').delink local getArgs = require('Module:Arguments').getArgs local p = {} local maplist={} local sgNames={} local highlightOption=false local highlightNum local visibleLinks -- This module creates framed maps of anywhere in the world, at the required scale, and enables annotations, -- dots, shapes, lines and other ways to customise the area of the map being shown. It also provides a link -- to an interactive fullscreen version, which has locator dots instead of annotations and shapes. -- This is the 2025 successor module to a wiki-markup template version of 2024, which itself was a successor -- to the 'Graph'/VEGA driven template that was begun in 2016, until the Vega version was switched off in 2023. -- This module is called from template {{OSM Location map}}, which uses the same parameter formats as before. -- In addition it will be possible to use a more concise parameter format using the template {{OSM Location dots}} -- In general the css output from the two formats will be identical, but the the concise version will allow bits of -- greater control over some of the settings. -- see the documentation on the two template pages for details of how to use the mapping features. -- If language customisation is needed, there are text items below that can be translated. Also see the color table -- below with details of how to add additional color names to allow localised alternatives. -- (Translating other language shape-types could be possible, but has not currently been contemplated. -- Parameter name translation would be harder but likely to be possible, ideally still retaining compatibility -- with template calls already written using English). local negativeAnswer={no=1,'0'-1,off=1} local fullscreenlinktext='Click for interactive fullscreen map with links to nearby articles' local toggletext='[Hide/show caption list]' local termsOfUse='Maps: terms of use' local aboutOSM='About OpenStreetMaps' local shapeList={} --This sets up the 'factoryDefault' shape group 0 shapeList["0"]={shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border of the shape outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items text settings for labels textSZ="11px", textCL="darkgrey", textNG="0deg", --sgf further text settings textSP="0px", textLH="120%", textOL="0px", textBG="transparent", --sgd items for dotTag text settings tagSize="10px", tagColor="white", tagSpacer="0px", tagAngle="0deg", --sge items for extension line to connect label to dot textEW="0px", textEC="darkgrey", textES="solid" } local colorList={} -- used by colorLookup to catch unsupported colors (eg 'LimeGreen'), to convert to generic version colorList['green']='hardgreen' -- it could also be added to to include alternative language equivelants, for a quick solution. colorList['red']='hardred' -- colorList ['source'] = target colorList['white']='white' -- converts any color that includes 'source' into its equivelent target colorList['blue']='hardblue' -- note, for translation you can add to this list, rather than replace it, colorList['brown']='brown' -- which would mean existing map definitions in english would also still work, alongside translated ones colorList['grey']='hardgrey' colorList['gray']='hardgrey' colorList['purple']='hardpurple' colorList['orange']='hardorange' colorList['leaf']='hardleaf' --for a more thorough translation, you can add all the variants of the colors as further CTB elements and hex values or redirects local CTB={} -- set up a table of color names (the CTB Color table index) and html hash colorhex values. CTB["paleblue"],CTB["softblue"],CTB["hardblue"],CTB["darkblue"]="#D6E1EC","#77A1CB","#4B77D6","#1c559e" CTB["palered"],CTB["softred"],CTB["hardred"],CTB["darkred"] = "#FCC6C0","#EC644B","#DB3123","#AA1205" CTB["palegreen"],CTB["softgreen"],CTB["hardgreen"],CTB["darkgreen"]= "#D2F0E5","#81AF81","#269F46","#0b7527" CTB["paleleaf"],CTB["softleaf"],CTB["hardleaf"],CTB["darkleaf"]= "#dff5c1","#b5e376","#8cc244","#679c21" CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"]= "#E8E8D6","#AAAA88","#777755","#444433" CTB["palegray"],CTB["softgray"],CTB["hardgray"],CTB["darkgray"]=CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"] CTB["palebrown"],CTB["softbrown"],CTB["hardbrown"],CTB["darkbrown"]="#FAF6ED","#CCB56C","#AD7F14","#754910" CTB["palepurple"],CTB["softpurple"],CTB["hardpurple"],CTB["darkpurple"]="#e0d1e6","#c784e0","#a029cf","#7a05a8" CTB["paleorange"],CTB["softorange"],CTB["hardorange"],CTB["darkorange"]="#ffedc2","#ffcf61","#EEB533","#e39f05" CTB["black"],CTB["white"],CTB["yellow"]="#000000","#FFFFFF","#FAF039" CTB["background"],CTB["paleground"],CTB["beigeground"]="#f9f6f2","#FEFEFA","#F5F5DC" CTB["beige"]=CTB["beigeground"] CTB["aqua"],CTB["teal"],CTB["fuchsia"] = "#00FFFF","#008080","#FF00FF" CTB["maroon"],CTB["olive"],CTB["navy"] = "#800000","#808000","#000080" CTB["lime"],CTB["limegreen"],CTB["aquamarine"] = "#00FF00","#32CD32","#7FFFD4" CTB["silver"],CTB["yellow"],CTB["orchid"] = "#800000","#FFFF00","#DA70D6" -- set up a table of predefined clip-paths local pathshape={} pathshape.squaredd = "M 19,1.25 l 0,18 -18,0 0,-18 18,0m-1,1 -16,0 0,16 16,0 0,-16m-1,1 0,14 -14,0 0,-14 14,0zm-1,1 -12,0 0,12 12,0 0,-12zm-1,1 0,10 -10,0 0,-10 10,0z" pathshape.squared = "M 18,2.5 l 0,15 -15,0 0,-15 15,0m-1,1 -13,0 0,13 13,0 0,-13zm-1,1 0,11 -11,0 0,-11 11,0z" pathshape.triangledd="M 0 20,20 20,10 0,0 20ZM1.5 19,10 1.7,18.5 19,1.5 19ZM3 18,17 18,10 3.8,3 18ZM4.5 17,10 5.4,15.4 17, 4.5,17ZM6 16,13.8 16,10 7.4z" pathshape.triangled ="M1,18 l 18,0 l -9,-18 l -9,18zm1.7,-1.1 l 7.3,-14.6 l 7.3,14.6 l -14.6, 0zm1.7,-1 l 11.0,0 l -5.5,-11 l -5.5,11z" pathshape.circledd = "M0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm0.8,0a9.2,9.2 0 1,1 18.4,0a9.2,9.2 0 1,1 -18.4,0m1,0a8.2,8.2 0 1,0 16.4,0a8.2,8.2 0 1,0 -16.4,0zm0.8,0a7.2,7.2 0 1,1 14.8,0a7.2,7.2 0 1,1 -14.8,0m1,0 a6.4,6.4 0 1,1 12.8,0a6.4,6.4 0 1,1 -12.8,0z" pathshape.circled = "M2.5,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0m0.8,0 a5,5 0 1,1 11.4,0a5,5 0 1,1 -11.4,0" pathshape.diamond = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10z" pathshape.diamondd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm1,0 l 6,8.5 l 6,-8.5 -6,-8.5 -6,8.5zm1,0 l 5,-7 5,7 -5,7 -5,-7z" pathshape.diamonddd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm0.75,0 l 6.25,9 l 6.25,-9 -6.25,-9 -6.25,9zm0.75,0 l 5.5,-8 5.5,8 -5.5,8 -5.5,-8zm0.75,0 l 4.75,7 l 4.75,-7 -4.75,-7 -4.75,7zm0.75,0 l 4,-6 4,6 -4,6 -4,-6z" pathshape.crossd = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0zM2.3,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.cross = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0z" pathshape.thincross = "M2,12 l6,0 l0,6 l4,0 l0,-6 l6,0 l0,-4 l-6,0 l0,-6 l-4,0 l0,6 l-6,0z" pathshape.fivepointstar = "M10 0 L12.245 6.91 19.511 6.91 13.633 11.18 15.878 18.09 10 13.82 4.122 18.09 6.367 11.18 0.489 6.91 7.755 6.91Z" pathshape.fivepointstard = "M10 1.5 L 11.90825 7.3735 18.08435 7.3735 13.08805 11.003 14.9963 16.8765 10 13.247 5.0037 16.8765 6.91195 11.003 1.91565 7.3735 8.09175 7.3735 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sixpointstar = "M10 0 L12.323 5.977 18.66 5 14.645 10 18.66 15 12.323 14.023 10 20 7.677 14.023 1.34 15 5.355 10 1.34 5 7.677 5.977Z" pathshape.sixpointstard = "M10 1.5 L 11.97455 6.58045 17.361 5.75 13.94825 10 17.361 14.25 11.97455 13.41955 10 18.5 8.02545 13.41955 2.639 14.25 6.05175 10 2.639 5.75 8.02545 6.58045 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sevenpointstar = "M10 0 L12.048 5.747 17.818 3.765 14.602 8.95 19.749 12.225 13.69 12.943 14.339 19.01 10 14.72 5.661 19.01 6.31 12.943 0.251 12.225 5.398 8.95 2.182 3.765 7.952 5.747Z" pathshape.sevenpointstard = "M10 1.5 L11.7408 6.38495 16.6453 4.70025 13.9117 9.1075 18.28665 11.89125 13.1365 12.50155 13.68815 17.6585 10 14.012 6.31185 17.6585 6.8635 12.50155 1.71335 11.89125 6.0883 9.1075 3.3547 4.70025 8.2592 6.38495 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.eightpointstar = "M10 0 L11.88 5.46 17.071 2.929 14.54 8.12 20 10 14.54 11.88 17.071 17.071 11.88 14.54 10 20 8.12 14.54 2.929 17.071 5.46 11.88 0 10 5.46 8.12 2.929 2.929 8.12 5.46Z" pathshape.eightpointstard = "M10 0 L10 1.5 L11.598 6.141 16.01035 3.98965 13.859 8.402 18.5 10 13.859 11.598 16.01035 16.01035 11.598 13.859 10 18.5 8.402 13.859 3.98965 16.01035 6.141 11.598 1.5 10 6.141 8.402 3.98965 3.98965 8.402 6.141ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.ring="M2.6,9.5a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.boxd=pathshape.squared pathshape.boxdd=pathshape.squaredd pathshape.ellipsed=pathshape.circled pathshape.ellipsedd=pathshape.circledd local msg={} local function debugmsg(txt) table.insert(msg,txt) end local pmsg={} local function previewMsg(txt) table.insert(pmsg,txt) end local function colorLookup(color) for c,l in pairs(colorList) do if string.find(color,c) then return l end end return color end local function getColor (color,chk) local c local opacity="100" if not color or color=='' then color='hardgrey' end if color=="transparent" then return color end if color=="background1" then color='background' end if string.byte(color,1,1)==35 and (#color == 7 or #color == 9) then c=color elseif string.byte(color,1,1)==35 and #color == 4 then c=string.sub(color,1,2)..'f'..string.sub(color,3,3)..'f'..string.sub(color,4,4)..'f' else local s=color..'1' s= s:sub(0,s:find("%d")-1) opacity=string.match(color,"%d+") if not CTB[s] then s= colorList[s] end -- check for synonyms and translations if not CTB[s] then debugmsg(mw.addWarning('color = '..color..'. The color name is not defined. Used default grey instead')) end c=CTB[s] or CTB.hardgrey end if opacity and (tonumber(opacity) < 100) and string.find(c,"#")==1 and string.len(c)==7 and opacity~="" then local hexval=string.format("%x",(math.floor(tonumber(opacity)*2.55))) c=c..hexval end return c end function p.colorvalue(frame) -- enable external access to the CTB colorTable values. usage: {{#invoke:OSM Location map|colorvalue|color=hard blue}} local c if not frame.args.color or frame.args.color=='' then c='grey' else c=string.lower(string.gsub(frame.args.color,'%s+','')) end return string.upper(string.sub(getColor(c),2)) end local function checkColors(color) local c=getColor(color,'check') local opacity =1 -- calculate colour brightness and return black or white for contrast if c=='transparent' then return c,'#000000',0 end if not (string.find(c,'#')==1) then return c,'#FFFFFF',0 end if #c>8 then opacity= tonumber('0x'..(string.sub(c,8,9)))/500 end local r=tonumber('0x'..(string.sub(c,2,3)))/255 local g=tonumber('0x'..(string.sub(c,4,5)))/255 local b=tonumber('0x'..(string.sub(c,6,7)))/255 if 0.2126 * r + 0.7152 * g + 0.0722 * b / opacity < 0.7 then return c,'#FFFFFF',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity else return c,'#000000',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity end end local function morethan(a,b) a = tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]') or '0') b = tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]') or '0') return a>b end local function lessthan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) b = tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]') or '0') end return a<b end local function getsize(size) --size1 is between 1 and 3 values, each with px, equating to width,height,corner-rounding --eg '15px 25px 5px' (spaces are optional) or '18px'. returns three numbers local sizeval = {} for v in string.gmatch(size, "[^px]+") do table.insert(sizeval,v) end sizeval[1] = tonumber(sizeval[1]) or 13 sizeval[2] = tonumber(sizeval[2]) or sizeval[1] sizeval[3] = tonumber(sizeval[3]) or 0 return sizeval[1],sizeval[2],sizeval[3] end local function coord2text(coord) -- looks through the output from {{coord}} to find the lat and long decimal values -- and converts compass points to minus or not-minus, return with separating comma. local lat = string.match(coord,'[%.%d]+°[NS]') local lon = string.match(coord,'[%.%d]+°[EW]') local neg={N="",S="-",W="-",E=""} return neg[string.match(lat, '[NS]')]..string.match(lat,'[%.%d]+')..","..neg[string.match(lon, '[EW]')]..string.match(lon,'[%.%d]+') end local function convertCoordsTrad (row) local coords='' if row and string.find (row,'<span class="geo">') then local a,b=string.find(row,'<span class="geo">') local start=b+1 a,b=string.find(row,"</span>",b) local finish=a-1 coords=string.sub(row,start,finish) coords=string.gsub(coords,'; ',',') end return coords end local function convertCoords (row) local start,finish,lat,lon,coords,says if row then local a,b=string.find(row,"<span class=") start=a while a do -- find the final span> finish=b a,b=string.find(row,"span>",b) end if start then coords= string.sub(row,start,finish) says="" if string.find(coords,'<span class="error">') then error("coord error: badly formed coordinates",0) end coords=coord2text(coords) coords = string.sub(row,1,start-1)..coords..string.sub(row,finish+1) else coords=row end return coords else return "Nothing to see here" end end local function fillCommas(val,max) local line='' if not val then line=',' -- ensure there is some content else line = val --string.lower(string.gsub(val,"%s+","")) -- or strip spaces end if string.find(line,',') == 1 then line=' '..line end -- ensure initial comma is not skipped local _, count=string.gsub(line,",","") -- add enough subsequent commas for all entries line=line..string.rep(',',max-count) while(string.find(line,",,") ) do line=string.gsub(line,",,",", ,") --ensure string.gmatch doesn't ignore any empty items by padding with spaces end return line end local function makeLinkBox(left,top,wid,label, link) local linkBoxName='Transparent square.svg' if visibleLinks or '' =='yes' then linkBoxName='Red hollow square.svg' end local builder = mw.html.create('div') --display:inline-block; builder :cssText('position:absolute;left:'..tostring(left-1-wid/2)..'px;top:'..tostring(top-1 + math.min(wid/2-12,0) - wid/2)..'px') :wikitext(string.format( '[[File:%s|%dpx|link=%s|%s]]', linkBoxName, wid+2, link, label )) return tostring(builder) end local function extractItem(row,searchItem) -- remove text following a searchItem or start of line, which might be in quote-marks to allow commas local xend,xstart=1,0 if not row then return '','' end if searchItem then xend,xstart= string.find(row or '',searchItem or 'image:') end if not xstart then return string.gsub(string.gsub(row or '',"%b\"\"", ''),"%b\'\'", '') or '','' end while row:byte(xstart+1) == 32 and xstart<#row do -- skip over any leading spaces xstart=xstart+1 end local xbyte=row:byte(xstart+1) if xbyte == 34 or xbyte == 39 then -- are they wrapped in single or double quotes xstart=xstart+1 xend=row:find(string.char(xbyte),xstart+1) else xend = row:find(',',xstart+1) -- if no quotes, we assume no commas if not xend then xend=#row+1 end end -- return residual row and extracted text return row:sub(0,xstart)..row:sub(xend), row:sub(xstart+1,xend-1) end local function itemCheck(item,ext) if not item then return nil end if not ext then ext='' end return (string.match(item,"[%.%-?%d]+") or '0')..ext end local function stripdivs(line) return string.gsub(line or '',"%b<>", ' ') end local function splitItem(item,max) -- takes a commas-sep list and returns a table of lowercase items with no spaces, or nil local r={} local x=1 item=string.lower(fillCommas(item,max)) for t in string.gmatch(item,"[^,]+") do r[x]=string.gsub(t,"%s+","") if r[x]=='' then r[x]= nil else -- residual items might have commas if x>max then r[max]=(r[max] or '')..', '..r[x] end end x=x+1 end return r end local function ParseShapeTypes (result,args,sgval) -- for use with compressed, comma-separated 'sg plus dots' parameters --[[ shape table items and default values as set at top of page shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items label text textSZ="11px", textCL="white", textNG="0deg", textAT=attributes ("bold" and/or "italic") --sgd items for dotTag tagSize="11px", tagColor="darkgrey", tagSpacer="0px", tagAngle="0deg", --sge extension line textEW="1px", textEC="darkblue", textES="solid" --sgf fx for text labels textSP="0px" textLH="100%" textOL="1px", textBG="paleground", <!--| sga = shape,Size,Color,Angle|sgb= outlineWidth,color,style eg: sga1=circle,14px,blue,0deg| sgb1=0px,dark grey,solid | sgc=textSize,color,angle,bold/italic | sgd=tagSize,Color,Spacer,Angle eg: sgc1=11px,dark grey,0deg,normal| sgd1=9px,white,0px,0deg | sge=lineWidth,color,style |sgf=textspacing,lineHeight%,outlinepx,backgroundcolor, [bold,italic] eg: sge1=0px,black, solid| sgf1=0px,120%,0px,background | dot=shape-group/lat/lon/title/dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> --]] if args["sgn"..sgval] then local sgname=string.match(args["sgn"..sgval],"(%w+)(.*)") sgNames[sgname]=sgval end local parent= args["sgp"..sgval] if parent then parent=string.match(parent,"(%w+)(.*)") local pos= string.find(parent,"%d+") if pos == 1 then parent=string.match(parent,"%d+") else parent=sgNames[parent] or '1' end end if sgval~='H' then if not parent or tonumber(parent) > tonumber(sgval) then if sgval=="1" then parent="0" else parent="1" end end end local itemTab, line, filename result[sgval]={} result[sgval].shapeType=sgval line,filename=extractItem(args['sga'..sgval] or '','image:') if sgval=='1' and not args.sga1 then line='circle,12px,blue,0deg' end -- ensure there is a parent=1 sga result[sgval].shapeFile=filename or '' -- sga= Attributes for shape itemTab=splitItem(line,4) result[sgval].shape = itemTab[1] or result[parent].shape result[sgval].shapeSize=itemTab[2] or result[parent].shapeSize result[sgval].shapeColor=itemTab[3] or result[parent].shapeColor result[sgval].shapeAngle=itemCheck(itemTab[4],'deg') or result[parent].shapeAngle -- sgb= Border outline attributes for shape itemTab=splitItem(args['sgb'..sgval],3) result[sgval].outlineWidth=itemCheck(itemTab[1],'px') or result[parent].outlineWidth result[sgval].outlineColor=itemTab[2] or result[parent].outlineColor result[sgval].outlineStyle=itemTab[3] or result[parent].outlineStyle --sgc=character attributes for label itemTab=splitItem(args['sgc'..sgval],4) result[sgval].textSZ=itemCheck(itemTab[1],'px') or result[parent].textSZ -- size of text in px result[sgval].textCL=itemTab[2] or result[parent].textCL -- colour for text result[sgval].textNG=itemCheck(itemTab[3],'deg') or result[parent].textNG -- Angle for text result[sgval].textAT=itemTab[4] or result[parent].textAT -- attributes bold, and/or italic --sgd=dotTag attributes itemTab=splitItem(args['sgd'..sgval],4) result[sgval].tagSize=itemCheck(itemTab[1],'px') or result[parent].tagSize result[sgval].tagColor=itemTab[2] or result[parent].tagColor result[sgval].tagSpacer=itemCheck(itemTab[3],'px') or result[parent].tagSpacer result[sgval].tagAngle=itemCheck(itemTab[4],'deg') or result[parent].tagAngle --sge= extension line attributes itemTab=splitItem(args['sge'..sgval],4) result[sgval].textEW=itemCheck(itemTab[1],'px') or result[parent].textEW -- width result[sgval].textEC=itemTab[2] or result[parent].textEC -- colour result[sgval].textES=itemTab[3] or result[parent].textES -- style --sgf= fx for label text itemTab=splitItem(args['sgf'..sgval],4) result[sgval].textSP=itemCheck(itemTab[1],'px') or result[parent].textSP -- spacing value for letters result[sgval].textLH=itemCheck(itemTab[2],'%') or result[parent].textLH -- Angle for text result[sgval].textOL=itemCheck(itemTab[3],'px') or result[parent].textOL -- width of text-border line result[sgval].textBG=itemTab[4] or result[parent].textBG -- color for text background return result end local function round(x,dec) -- x=number [, dec=integer] returns numeric value with upto dec decimals (all but first trailing zeros get truncated) if (dec or 0)==0 then return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5) --this avoids .0 where dec=0 end dec =10^(dec) return x>=0 and math.floor(x*dec+0.5)/dec or math.ceil(x*dec-0.5)/dec end local function maptogrid(t, r) r = r or 2 if type(t) ~= "table" then return 0, 0 end local lon = tonumber(t.lon) local lat = tonumber(t.lat) local lonbase = tonumber(t.lonbase) or lon local latbase = tonumber(t.latbase) or lat local zoom = tonumber(t.zoom) or 0 local width = tonumber(t.width) or 0 local height = tonumber(t.height) or 0 if not lon or not lat then return 0, 0 end local cosBase = math.cos(math.rad(latbase)) if cosBase == 0 or cosBase ~= cosBase then cosBase = 1 end local scale = 156543.03 * cosBase / (2 ^ zoom) if scale == 0 then scale = 1 end local x = width / 2 + ( ((math.rad(lon) * 6378137) - (math.rad(lonbase) * 6378137)) / scale ) * (1 - (0.075 * (math.abs(latbase) / 90))) local y = height / 2 + ( ((math.log(math.tan(math.rad(latbase) / 2 + math.pi / 4)) * 6378137) - (math.log(math.tan(math.rad(lat) / 2 + math.pi / 4)) * 6378137)) / scale ) * (1 - (0.075 * (math.abs(latbase) / 90))) return round(x, r), round(y, r) end local function getScale(zoom, lat, magVal) zoom = tonumber(zoom) or 0 lat = tonumber(lat) if not lat then lat = 0 -- fallback end if magVal then magVal = tonumber(magVal) if magVal > 1 and magVal <= 2 then zoom = zoom + (magVal - 1) end end local dist = (156543.03 * math.cos(math.rad(math.abs(lat))) / (2 ^ zoom)) / 17 local y if dist < 1 then y = round(dist * 10, 1) return tostring(y * 100) .. 'm', tostring(round(y * 109, 0)) .. 'yds' elseif dist < 18 then y = round(dist, 0) return tostring(y) .. 'km', tostring(round(y * 0.621371, 1)) .. 'miles' elseif dist < 500 then y = round(dist / 10) return tostring(y * 10) .. 'km', tostring(round(y * 6.21371, 0)) .. 'miles' else y = round(dist / 100) return tostring(y * 100) .. 'km', tostring(round(y * 62.1371, 0)) .. 'miles' end end local function ParseData (args,dotval) -- for use with compressed, comma-separated 'sg plus dots' parameters -- takes a structured series of comma-separated items which get parsed as the following: -- dot(n)= (sgNumber or Name),{{coord}} or (lat,lon), (dotTag) -- dotlink(n) = single-parameter text to give wikilink and/or title used by tootlip, fullscreen dots and autocaption list -- dotlabel(n) = 'label text',pos(left,roght,top,bottom,centre,auto),(dx), (dy) pixel offsets, params, info -- dotpic(n) = single parameter wikimedia filename for an image to use in photopanel and/or fullscreen dots -- dotfeature(n)= 'mark-line' (,linewidth,style,gap) or 'photo-panel' (,image-dim,panel-width,panel-height), draws line to n-1 -- label is used if either a position and/or an x,y offset are not 0,0 ( if no label then dotTag will be put at at the x,y offset, or over the dot -- label text can be autoaligned if x,y puts it left or right of the dot, or centered if above/below) -- quote marks are not needed unless including commas within the label text -- param1 is optional items separated by spaces, and can include [nolabel nolist nomap hemisphere+1 hemisphere-1] -- info is free wikitext, to be used in the fullscreen dot box. (use dotpic to show a picture) --<!--| dotx=shape-group,[lat,lon or {{coord}} ], dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> local result={} local count=1 local row = convertCoords (args["dot"..dotval]) -- swap in any {{coord}} values so they are csv lat and lon row=fillCommas(row,4) result.code=dotval -- store the parameter name as the id code for item in string.gmatch(row,"[^,]+") do -- iterate through 'row', adding each csv item in turn, if present if count==1 then --see if it is a number or a name local pos= string.find(item,"%d+") if pos == 1 then result.group=string.lower(string.gsub(item,"%s+","")) else item=string.match(item,"(%w+)(.*)") -- ensure just a single word result.group=sgNames[item] end elseif count==2 then result.lat=tonumber(string.match(item,"[%.%-?%d]+")) or 0-- find the number, with no non-numeric stuff elseif count==3 then result.lon=tonumber(string.match(item,"[%.%-?%d]+")) or 0 elseif count==4 then result.dotTag=item:match( "^%s*(.-)%s*$" ) or "" -- dotTag allows for internal spaces, but no commas end count=count+1 end row, result.labelText= extractItem(args["dotlabel"..dotval]) result.labelText= string.gsub(result.labelText,"[%^]+","<br>") -- convert hats to line breaks local item=splitItem(row,6) result.labelPos=item[2] or 'center' result.dx=tonumber(string.match(item[3] or '0',"[%.%-?%d]+")) or 0 result.dy=tonumber(string.match(item[4] or '0',"[%.%-?%d]+")) or 0 result.param1=string.lower(item[5] or '') if string.find(result.param1,"hemisphere-1",1,true) then result.lon=result.lon-360 elseif string.find(result.param1,"hemisphere+1",1,true) then result.lon=result.lon+360 end local txt = '' if item[6] then -- ensure all info elements are included, including commas count=1 local max=6 for t1 in string.gmatch(fillCommas(row,max),"[^,]+") do if count>max then txt=txt..',' end if count>=max then txt=txt..t1 end count=count+1 end end result.info=txt result.imageName = args['dotpic'..dotval] -- Get first wikilinked item (if any) from the args.dotlink and set this plus the delinked text local testx=args["dotlink"..dotval] or '' result.dotLink=testx if testx ~= '' then testx=stripdivs(testx) result.title=delink({ testx }) local linkstart= string.find(testx,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then result.dlink=delink( { string.sub(testx,linkstart,string.find(testx,']]',1,true)+1),wikilinks='target' } ) else result.dlink='' end else result.dlink='' result.title='' end if args['dotfeature'..dotval] then local item=splitItem(args['dotfeature'..dotval],6) if (item[1] or '') =='photo-panel' then result.ppwidth= tonumber(string.match((item[3] or '110'),"%d+")) -- panel width result.ppheight= tonumber(string.match((item[4] or '48'),"%d+") ) --panel height result.photowidth=round(tonumber(string.match((item[2] or '1.3'),"[%.%-?%d]+")) * result.ppheight+1,0) -- dimension to set image size result.photoImage=result.imageName result.posType='photo-panel' elseif (item[1] or '') == 'mark-line' then local x=tonumber(dotval or '0') result.markDest=item[5] or tostring(x-1) result.mlWidth= tonumber(string.match((item[2]) or '',"%d+") or '1') result.mlStyle= item[3] or 'solid' result.mlGap=tonumber(string.match((item[4] or ''),"[%d]+") or '0') result.posType='mark-line' -- debugmsg('making line for '..tostring(x)..'to '..tostring(result.markDest)..' with width '..tostring(result.mlWidth)) end --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end maplist.lon=result.lon maplist.lat=result.lat result.gridx, result.gridy = maptogrid(maplist,1) -- convert geo coords to grid xy - using values from maplist table return result end local function multiCheck (args, argName, argVal, defVal, alt) if not alt then alt='nonexistant' end if argVal=='H' and not args[argName..'H'] then argVal=highlightNum or '1' end if argVal=='' then return (args[argName] or args[alt] or (args[argName..'D']) or args[alt..'D'] or defVal) -- unnumbered args do not inherit from D or 1 else return (args[argName..argVal]) or (args[alt..argVal]) or (args[argName..'D']) or (args[alt..'D']) or (args[argName..'1']) or (args[alt..'1']) or defVal end end local function assignTradstyleShape(shapeResult,default,dotResult,args,nval) local item,itemTab local autoDotTag='' local shapeWidth,shapeHeight=0,0 local argval=nval -- to catch the unnumbered shape series if argval=='0' then argval='' end if nval=='H' then shapeResult.H={} end item=string.lower(multiCheck(args,'shape',argval,'image')) if string.find(item,'n-',0,true)==1 or string.find(item,'l-',0,true)==1 then autoDotTag=string.sub(item,0,1) item=string.sub(item,3) end if item == 'image' then shapeResult[nval].shape = 'image:' shapeResult[nval].shapeFile =multiCheck(args,'mark',argval,'Red pog.svg') shapeWidth=-1 else shapeResult[nval].shape = item end item= multiCheck(args,'mark-size',argval,'14px') local a,b,c= getsize(string.gsub(string.gsub(item,',','px')..'px','pxpx','px')) if b==a and args['mark-dim'..argval] then b= b / tonumber(string.match(args['mark-dim'..argval],"[%.%-?%d]+")) end shapeHeight=b/2 item=tostring(a)..'px'..tostring(b)..'px'..tostring(c)..'px' shapeResult[nval].shapeSize= item itemTab=splitItem(multiCheck(args,'shape-color',argval,'hard red'),2) shapeResult[nval].shapeColor=itemTab[1] or 'hardred' item=itemCheck(itemTab[2],'%') -- jump through the various opacity hoops and add to color if needed if not item then item=itemCheck(args['shape-opacity'..argval],'%') end if item and item~='0%' and item~='100%' then shapeResult[nval].shapeColor=shapeResult[nval].shapeColor..item end shapeResult[nval].shapeAngle=itemCheck(multiCheck(args,'shape-angle',argval,'0'),'deg') or '0deg' --sort out the outline entry itemTab=splitItem(multiCheck(args,'shape-outline',argval,'transparent,0,100,solid'),4) shapeResult[nval].outlineColor=itemTab[1] or 'dark grey' shapeResult[nval].outlineWidth=itemCheck(itemTab[2],'px') or '1px' if itemTab[3] and itemTab[3]~='100' and itemTab[3]~='0' then shapeResult[nval].outlineColor=shapeResult[nval].outlineColor..itemCheck(itemTab[3],'%') end shapeResult[nval].outlineStyle=itemTab[4] or 'solid' -- label size, background, outline itemTab=splitItem( multiCheck(args,'label-size',argval,'12'),3) shapeResult[nval].textSZ=itemCheck(itemTab[1],'px') or '12px' if itemTab[2]=='outline' then shapeResult[nval].textBG=itemTab[3] or 'transparent' shapeResult[nval].textOL='1px' elseif itemTab[3]=='outline' then shapeResult[nval].textBG=itemTab[2] or 'transparent' shapeResult[nval].textOL='1px' else shapeResult[nval].textOL='0px' shapeResult[nval].textBG=itemTab[2] or 'transparent' end if getColor(shapeResult[nval].textBG)==CTB['hardgrey'] and shapeResult[nval].textBG~='hardgrey' then shapeResult[nval].textBG= 'transparent' end --label color etc itemTab=splitItem(multiCheck(args,'label-color',argval, 'darkgrey','label-colour'),2) shapeResult[nval].textCL=itemTab[1] or 'darkgrey' if itemTab[2] and itemTab[2]~='0%' and itemTab[2]~='100%' then shapeResult[nval].textCL=shapeResult[nval].textCL..itemTab[2] end shapeResult[nval].textSP=itemCheck( multiCheck(args,'label-spacing',argval,'0'),'px') -- sets letter-spacing in px shapeResult[nval].textLH=itemCheck( multiCheck(args,'label-height',argval,'120'),'%') -- sets line height, 120% default shapeResult[nval].textNG=itemCheck(multiCheck(args,'label-angle',argval,'0'),'deg') --sgd=dotTag attributes shapeResult[nval].tagSize=tostring(shapeHeight*1.5)..'px' local c1,c2=checkColors(shapeResult[nval].shapeColor) shapeResult[nval].tagColor=c2 shapeResult[nval].tagSpacer='0px' shapeResult[nval].tagAngle='0deg' -- sge extension line attributes local shapePos=splitItem(multiCheck(args,'label-pos',argval,'right'),6) if shapePos[2]=='with-line' or shapePos[2]=='n-line' then shapeResult[nval].textEW=(shapePos[3] or '1')..'px' -- width shapeResult[nval].textEC=(shapePos[4] or shapeResult[nval].shapeColor or 'darkgrey') elseif shapePos[2]=='photo-panel' then shapeResult[nval].textEW='2px' -- width shapeResult[nval].textEC=shapeResult[nval].textCL else shapeResult[nval].textEW='0px' -- width shapeResult[nval].textEC='grey'-- colour end shapeResult[nval].textES='solid' if argval=='H' then return dotResult end --Assign dot values local dotItem={} dotItem.group=nval dotItem.code=nval dotItem.posType=shapePos[2] or 'nil' if (shapePos[2] or '') =='photo-panel' then dotItem.ppwidth= tonumber(string.match((shapePos[4] or '110'),"%d+")) dotItem.ppheight= tonumber(string.match((shapePos[5] or '48'),"%d+") ) dotItem.photowidth=round(tonumber(string.match((shapePos[3] or '1.3'),"[%.%-?%d]+")) * dotItem.ppheight+1,0) dotItem.photoImage=args['mark-image'..argval] --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end if (shapePos[2] or '') =='mark-line' then local x=tonumber(argval or '1') dotItem.markDest=shapePos[6] or tostring(x-1) dotItem.mlWidth= tonumber(string.match((shapePos[3] or '1'),"%d+")) dotItem.mlStyle= shapePos[4] or 'solid' dotItem.mlGap=tonumber(string.match((shapePos[5] or '0'),"[%d]+")) shapeResult[nval].textEC=shapeResult[nval].outlineColor or 'darkgrey' end if args['mark-coord'..argval] then local raw = args['mark-coord'..argval] local lat, lon lat, lon = string.match(raw, "{{coord|([%-%.%d]+)|([%-%.%d]+)}}") if not lat or not lon then lat, lon = string.match(raw, "([%-%.%d]+)%s*[,;]%s*([%-%.%d]+)") end if not lat or not lon then local cleaned = convertCoordsTrad(raw) if cleaned then local tab = splitItem(cleaned, 2) lat = tab[1] and string.match(tab[1], "[%-%.%d]+") lon = tab[2] and string.match(tab[2], "[%-%.%d]+") end end dotItem.lat = tonumber(lat) or 0 dotItem.lon = tonumber(lon) or 0 else dotItem.lat = tonumber(string.match(args['mark-lat'..argval] or "", "[%-%.%d]+")) or 0 dotItem.lon = tonumber(string.match(args['mark-lon'..argval] or "", "[%-%.%d]+")) or 0 end if args['dateline'..argval] and (args['dateline'..argval]=='1' or args['dateline'..argval]=='-1') then dotItem.lon=dotItem.lon+(tonumber(args['dateline'..argval] ) *360) end maplist.lon=dotItem.lon maplist.lat=dotItem.lat dotItem.gridx, dotItem.gridy = maptogrid(maplist,1) local item=args['mark-title'..argval] or '' -- sort out the caption, wikilink and plaintext tooltip items from dotLink if item=='none' then dotItem.param1='nomap nolist' item='' end dotItem.dotLink=item if item ~= '' then item=stripdivs(item) dotItem.title=delink({item}) local linkstart= string.find(item,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then dotItem.dlink=delink({string.sub(item,linkstart,string.find(item,']]',1,true)+1),wikilinks='target'}) else dotItem.dlink='' end else dotItem.dlink='' dotItem.title='' end if autoDotTag=='n' then item=nval elseif autoDotTag=='l' then item=string.char(64+tonumber(nval)) else item='' end dotItem.dotTag = args['numbered'..argval] or item if shapePos[2]=='n-line' and (args['label'..argval] or args['label'..argval]=='') then if dotItem.dlink =='' then item=dotItem.dotTag..' '..args['label'..argval] else item='[['..dotItem.dlink..'|'..dotItem.dotTag..']] '..args['label'..argval] end else item=(args['label'..argval] or '') end if args['labela'..argval] then item = item..'^'..args['labela'..argval] end if args['labelb'..argval] then item = item..'^'..args['labelb'..argval] end local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end dotItem.labelText = a -- convert hats to line breaks if argval=='' then item = (args['label-offset-x']) or (args.ldx) or '0' else item=args['label-offset-x'..argval] or args['ldx'..argval] or args['label-offset-xD'] or args.ldxD or args['label-offset-x1'] or args.ldx1 or '0' end dotItem.dx=tonumber(string.match(item,"[%.%-?%d]+")) if argval=='' then item = (args['label-offset-y']) or (args.ldy) or '0' else item=args['label-offset-y'..argval] or args['ldy'..argval] or args['label-offset-yD'] or args.ldyD or args['label-offset-y1'] or args.ldy1 or '0' end dotItem.dy=tonumber(string.match(item,"[%.%-?%d]+")) dotItem.labelPos=shapePos[1] if args['mark-image'..argval] then dotItem.imageName= args['mark-image'..argval] end dotItem.info=args['mark-description'..argval] or '' table.insert(dotResult,1,dotItem) -- add to start of list, so they are in reverese order for displaying return dotResult end local function tradstyleParseShapes(args,dotTable,dotmax) local sgNumbers,sgSortable={},{} for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) then sgNumbers[x]=x -- add it to the list end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form TODO this must be possible in a single list! if args['mark-coord'] or (args['mark-lat'] and args['mark-lon']) then table.insert(sgSortable,'0') end -- add it to the end of the list local default={} default.D={} local dotResult={} for k,sgnum in pairs(sgSortable) do -- work through the sorted list, parsing each set of shapes in turn, from 1 upwards shapeList[sgnum]={} dotTable=assignTradstyleShape(shapeList,default,dotTable,args,sgnum) end dotTable=assignTradstyleShape(shapeList,default,dotTable,args,'H') -- construct an extra highlight shapeitem return dotTable end local function checkfortooltip (title,dx,dy,dotlabel,dlink,nolabel) -- returns tlink if available, and dlink, if needed and tshape=true if shape needed local tshape,tlink = false,"" if dlink~='' and not nolabel then tlink=dlink end if (tlink=="" or nolabel) and title~="" then tshape=true end -- tshape flags True if title is wanted for shape if (not (dx==0 or dy==0) or dotlabel=='') and title~='' then tshape=true end -- add tooltip to shape if its number has moved return tshape,tlink end local function tshift(angle) -- adjustment to place text near the centre of a triangle, shifted to allow rotation of triangle shape local x=tonumber(string.match(angle,"%-?%d+")) if x<0 then x=360+x end -- set to a single degree direction, 0 to 360 if x>359 then return 0,0 end -- shift the centre of the triangle based on rotation value if x <=40 or x>=320 then return -0.17,0 -- triangle up= -shiftv elseif x>=140 and x<=220 then return 0.17,0 --triangle down= +shiftv elseif x >220 then return 0,-0.17 --triangle left= -shifth elseif x >40 then return 0,0.22 --triangle right= +shifth end return 0,0 end local function makeTriangle(result,row,shape,outline,tlink) local w,h,r=getsize(shape.shapeSize) if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) w=w+p*2 h=h+p*2 end table.insert(result,'<div ') if tlink then table.insert(result,' title="'..row.title..'" ') end table.insert(result,'style="display:inline-block; position: absolute') if shape.shapeAngle ~= '0deg' then table.insert(result,'; transform: rotate('..shape.shapeAngle..')') end local shiftv,shifth=0,0 shiftv,shifth=tshift(shape.shapeAngle) table.insert(result,'; top: '..tostring(row.gridy-h/2+h*shiftv)..'px') table.insert(result,'; left: '..tostring(row.gridx-w/2+w*shifth)..'px; width: 0; height: 0; outline-width: 0px') table.insert(result,'; border-left: '..tostring(w/2)..'px solid transparent') table.insert(result,'; border-right: '..tostring(w/2)..'px solid transparent') if outline then -- fill with outline colour, to make a 'base layer' or shape colour table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.outlineColor).. '">') else table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.shapeColor).. '">') end table.insert(result,'</div>') end local function makeSquare(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) if shape.shape=='square' then h=w end -- squares are always square!. box can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end if r~=0 then div:css('border-radius',tostring(r).."px") end if shape.shape=='panel' then div:css('top', tostring(row.gridy).."px") div:css('left', tostring(row.gridx).."px") else div:css('top', tostring(row.gridy-h/2).."px") div:css('left', tostring(row.gridx-w/2).."px") end div:css('width', tostring(w).."px") div:css('height', tostring(h).."px") div:css('background-color', getColor(shape.shapeColor) ) div:css('color', 'inherit') table.insert(result,tostring(div)) end local function makeCircle(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding if shape.shape=='circle' then h=w end -- circles are always round. ellipse can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end div :css('top', tostring(row.gridy-h/2).."px") :css('left', tostring(row.gridx-w/2).."px") :css('width', tostring(w).."px") :css('height', tostring(h).."px") :css('border-radius', "50%") :css('background-color', getColor(shape.shapeColor) ) :css('color', 'inherit') table.insert(result,tostring(div)) end local function makeRuleA(result,row,shape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local lineV=0 if shape.shape=='rulea' then lineV=oWid*3+16 end table.insert(result,"<div style=\"display:inline-block; position: absolute") -- create a square transparent container, which will rotate line and arrow together table.insert(result,"; top:"..tostring(row.gridy - w/2).."px") table.insert(result,"; left:"..tostring(row.gridx - w/2).."px") table.insert(result,"; width:"..tostring(w).."px") table.insert(result,"; height:"..tostring(w).."px; background:transparent; color:inherit;") table.insert(result,"; transform: rotate( "..tostring(tonumber(string.match(shape.shapeAngle,"[%.%-?%d]+")) - 90).."deg);\">" ) table.insert(result,"<div style=\"display:inline-block; position: absolute") -- put the line (as a border-right) across the container table.insert(result,"; top:"..tostring(lineV).."px") table.insert(result,"; left:"..tostring((w - oWid )/2).."px; width: 0px") table.insert(result,"; height: "..tostring(w -lineV).."px") table.insert(result,"; border-right: "..shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) table.insert(result,"; background:transparent; color:inherit;\"></div>") if shape.shape=='rulea' then table.insert(result,"<div style=\"display:inline-block; position: absolute; top: 0px") --and add arrow head table.insert(result,"; left:"..tostring(w/2-( oWid/2)-oWid*0.55-2).."px; width: 0; height: 0; outline-width: 0px") table.insert(result,"; border-left: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-right: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-bottom: "..tostring(oWid*3+16).."px solid "..getColor(shape.outlineColor).."\"></div>") end table.insert(result,"</div>") end local function makeCurveA(result,row,shape) -- draw a curve with Arrow ----- local w,h=getsize(shape.shapeSize) -- = width,height local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local Angle=tonumber(string.match(shape.shapeAngle,"[%.%d]+")) table.insert(result,'<div style="position: absolute;') --set up out div, which will allow the whole to rotate table.insert(result,'top:'..tostring(row.gridy - (w + oWid*3+16)/2)..'px;') table.insert(result,'left:'..tostring(row.gridx - ( w + oWid*3+16)/2)..'px; ') table.insert(result,'width: '..tostring(w+oWid*3+16)..'px; ') table.insert(result,'height: '..tostring(w+oWid*3+16)..'px; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate( '..tostring(Angle-120)..'deg);">') else table.insert(result,'transform: rotate( '..tostring(Angle -62)..'deg);">') end table.insert(result,'<div style="position: absolute;') --set up div for the rounded corner of a rectangle table.insert(result,'border-left: '..shape.outlineWidth..' '..shape.outlineStyle..' '..getColor(shape.outlineColor)..';') if shape.shape=='curvea' then table.insert(result,'border-radius: 10000px 0 0 '..tostring(w)..'px; top:0px; left:'..tostring(w*0.25)..'px;') else table.insert(result,'border-radius: '..tostring(w)..'px 0 0 10000px;') table.insert(result,'top:'..tostring((oWid*3+16)/2+w*0.15)..'px; left:'..tostring(w*0.25)..'px;') end -- and add a triangular arrow head table.insert(result,'width: '..tostring(w)..'px; height: '..tostring(w)..'px;"></div><div style="position: absolute; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate(180deg); top:'..tostring(w-1)..'px; ') else table.insert(result,'transform: rotate(0deg); top: '..tostring(0-( ( oWid*3+16)/2)+1+( w*0.15) )..'px;') end -- reverse table.insert(result,'left:'..tostring(0-( oWid*0.6)-2+(w*0.25))..'px;') table.insert(result,'width: 0; height: 0; outline-width: 0px; border-left: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-right: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-bottom: '..tostring(oWid*3+16)..'px solid '..getColor(shape.outlineColor)..';"></div></div>') end local function makeLineTo (result,x1,y1,x2,y2,oWid, oStyle, oCol,double) table.insert(result,"<div style=\"display:inline-block; position: absolute;") -- draw a line between x1,y1 and x2,y2, px-coords where 0,0 is centre of frame -- Maths calculations thanks to ES table.insert(result,"left: "..tostring(x1+( (x2-x1)/2) - (math.sqrt( ( x2-x1)^2 + (y2-y1)^2 )/2)-1).."px;") table.insert(result,"top: "..tostring(y1+( ( y2-y1 )/2) ).."px;") table.insert(result,"width: "..tostring(math.sqrt( (x2-x1 )^2 + ( y2-y1 )^2) ).."px;") table.insert(result,"height: "..tostring(double).."px; background-color:transparent; color:inherit; ") table.insert(result,"outline-width: 0; border-bottom: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) if double>1 then table.insert(result,"border-top: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) end if x1==x2 then table.insert(result,"transform: rotate(90deg);") else table.insert(result,"transform: rotate("..tostring(math.atan(( y2-y1)/( x2-x1 ) )*180/math.pi).."deg);\"></div>") end end local function makeClipPath(result,row,shape,outline,tshape) --tshape is a flag to show if the tooltip (title=) is wanted -- return the text css div code to position and draw a shape occupying a specified clippath if not pathshape[shape.shape] then debugmsg(mw.addWarning('shape'..row.code..' = '..shape.shape..'. The shape name is not defined')) return end local w,h,r=getsize(shape.shapeSize) if string.match(shape.shape,"circle") or string.match(shape.shape,"square") then h=w end -- use ellipse and box for stretched shapes local shifth,shiftv = 0,0 if string.match(shape.shape,"triangle") then shiftv,shifth =tshift(shape.shapeAngle) end if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) or 0 w=w+p*2 h=h+p*2 end table.insert(result,"<div ") if tshape then -- Add tooltip if needed table.insert(result," title=\""..row.title.."\" ") end table.insert(result,"style=\"display:inline-block; position: absolute; background-color:") if outline then table.insert(result,getColor(shape.outlineColor)) -- fill with outline colour, to make a 'base layer' else table.insert(result,getColor(shape.shapeColor)) end table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;"..pathshape[shape.shape].."&#39;) ") -- adds the required clippath data from the table of pathshape string literals table.insert(result,"; top:"..tostring(row.gridy - 10 + h*shiftv).."px") table.insert(result,"; left:"..tostring(row.gridx - 10 + w*shifth).."px") table.insert(result,"; width:20px") -- needs to be a path within a 20px20px box, and then rescales using size values to match other shape sizes table.insert(result,"; height:20px; transform:scale("..tostring(w/16)..", "..tostring(h/16)..")") if shape.shapeAngle ~= "0deg" then table.insert(result," rotate("..shape.shapeAngle..")") end table.insert(result,"\"></div>") end local function makeImage(result,row,shape) local w,h,r=getsize(shape.shapeSize) local image=shape.shapeFile if not image or image=='' then image='Red pog.svg' end local imagediv=mw.html.create ('div') imagediv:css('position', "absolute") if shape.shapeAngle ~= "0deg" then imagediv:css('transform',"rotate("..shape.shapeAngle..")") end imagediv :css('top', (row.gridy-1 + math.min(h/2-12,0) - h/2).."px") --File seems to adjust pos for small images :css('left', (row.gridx-1-w/2).."px") :css('background-color', "transparent" ) :css('color','inherit') :wikitext('[[file:'..image..'|'..tostring(w+2)..'px|alt='..(row.title or '')..'|link=]]') table.insert(result,tostring(imagediv)) end local function makePhotoPanel(result,row,shape) local h=row.ppheight table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;' ) table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') table.insert(result, 'width: '..tostring(row.ppwidth)..'px; height: '..tostring(h)..'px; border-radius: 2px; color:inherit;') table.insert(result, 'background-color: #E8E8D6; outline: 2px solid '..getColor(shape.textCL)..'; box-shadow: 2px 2px 4px #33203335;"></div>') if row.photoImage and row.photowidth >0 then table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;') if row.labelPos=='left' or string.find(row.labelPos,'west') then --debugmsg(row.labelText..', Align=right, ppwidth='..tostring(row.ppwidth)..', photowidth='..row.photowidth..', dx='..tostring(row.dx)..', dy='..tostring(row.dy)) table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') row.dx=row.dx+row.photowidth/2 else --debugmsg(row.labelText..', Align=left, ppwidth='..tostring(row.ppwidth)..', photowidth='..row.photowidth..', dx='..tostring(row.dx)..', dy='..tostring(row.dy)) table.insert(result, 'left: '..tostring(row.gridx+row.dx + (row.ppwidth-row.photowidth) - row.ppwidth/2)..'px;') row.dx=row.dx-row.photowidth/2 end table.insert(result, 'background-color:transparent; color:inherit; border-radius: 2px;">') table.insert(result,'[[File:'..row.photoImage..'|x'..tostring(h)..'px|File:'..row.photoImage..']]</div>') row.labelPos='center' end end local function makePanelText(result, row, shape) local w,h,r=getsize(shape.shapeSize) local ty=tonumber(string.match(shape.textSZ or '11',"%d+") ) table.insert(result,'<div style="position:absolute; line-height: 120%; font-size: '..shape.textSZ..'; color:'..getColor(shape.textCL)..';') if row.labelPos == 'left' or string.find(row.labelPos,'west') then table.insert(result,'top: '..tostring(row.dy+row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+3)..'px; text-align: left; width:'..tostring(w)..'px;') elseif row.labelPos == 'right' or string.find(row.labelPos,'east') then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+w-3)..'px; text-align: right; width: max-content; transform: translateX(-100%);') elseif row.labelPos == 'top' or row.labelPos== 'north' then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') elseif row.labelPos == 'bottom' or row.labelPos=='south' then local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*1.1 table.insert(result,'top: '..tostring(row.gridy+w-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') else -- center or centre local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*0.6 table.insert(result,'top: '..tostring(row.gridy+(h/2)-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') end if shape.textSP and shape.textSP ~='0px' then table.insert(result,"letter-spacing: "..shape.textSP..';') end if shape.textLH and shape.textLH ~='120%' then table.insert(result,"line-height: "..shape.textLH..';') end table.insert(result,"vertical-align: middle;\">"..row.labelText.."</div>") end local function makeTextItem(result, row, shape, align, tlink, textItem, dotItem) local w,h,r=getsize(shape.shapeSize) table.insert(result,"<div ") if row.title ~= "" then table.insert(result," title=\""..row.title.."\" ") end local ty,bry,linkoffset = 0,0,0 local compy=0 local lh=tonumber(string.match(shape.textLH or '120',"%d+"))/100 if dotItem==1 or (dotItem==2 and row.posType~='n-line') then -- if there is a dotTag in the middle of the shape then use the tag settings ty=tonumber(string.match(shape.tagSize,"%d+")) or 0 table.insert(result,"style=\"position:absolute; line-height: 120%; top: "..tostring(row.gridy-ty*0.62)) table.insert(result,"px; left: "..tostring(row.gridx).."px; width: fit-content; ") table.insert(result,"text-align: center; color: "..getColor(shape.tagColor).."; background-color: transparent;") local trf="" if shape.tagAngle ~="0deg" then trf=" rotate("..shape.tagAngle..")" end table.insert(result, "transform: translateX(-50%)"..trf.."; font-size: "..shape.tagSize..";") if shape.tagSpacer~='0px' then table.insert(result, "letter-spacing:"..shape.tagSpacer..";") end else -- or add tfx settings for left, right or center align, colors, backgrounds, border-outline table.insert(result,'style="position:absolute; ') if dotItem==2 then -- dotTag is out at x,y so 85% ty=tonumber(string.match(shape.tagSize or '0',"%d+")) table.insert(result,'font-size: '..shape.tagSize..'; padding:0px 2px;line-height: 85%; top: '..tostring(row.dy+row.gridy-ty*0.52)) else -- it is labelText, so use textLH or 120% ty=tonumber(string.match(shape.textSZ or '11',"%d+")) if row.labelPos=='northwest' or row.labelPos=='northeast' then compy=-ty elseif row.labelPos=='southeast' or row.labelPos=='southwest' then compy=ty/2 end if row.labelPos and not (row.labelPos== 'auto' or row.labelPos=='') then bry=(select(2, string.gsub(row.labelText,"<br>", ""))*lh) -- is it a multiline text? expand by line-height /120%? if row.posType == 'with-line' then bry=0 end if row.labelPos=='bottom' or row.labelPos == 'south' then bry = 0 -- and shift by none, all or half elseif row.labelPos=='top' or row.labelPos == 'north' then if shape.shape=='image:' then bry= 1 + math.min(w/2-10,0)-bry*ty else bry= -bry*ty+2 end else bry=-bry*(ty/2 * lh) end end if row.posType == 'photo-panel' then bry=bry+3 end table.insert(result,'font-size: '..shape.textSZ..'; padding:0px 3px;line-height: '..shape.textLH..'; ') table.insert(result,'top: '..tostring(row.dy+row.gridy+compy+bry-ty*lh/2)) end table.insert(result,"px; left: "..tostring(row.dx+row.gridx).."px; color: "..getColor(shape.textCL).."; ") table.insert(result,"width: max-content; ") local trf="" if shape.textNG ~="0deg" then trf="rotate("..shape.textNG..")" end if shape.textOL~="0px" then table.insert(result,"background-color: "..getColor(shape.textBG).."; ") table.insert(result,"border: "..shape.textOL.." solid "..getColor(shape.textCL).."; border-radius:6px;") else table.insert(result,"background-color: transparent;") end if row.labelPos=="right" or string.find(row.labelPos,'east') then table.insert(result,"text-align: left; ") linkoffset=w if shape.textNG ~="0px" then table.insert(result,"transform-origin: left; transform: rotate("..shape.textNG.."); ") end elseif row.labelPos=="left" or string.find(row.labelPos,'west') then if shape.textNG ~="0px" then table.insert(result,"transform-origin: right;") end linkoffset=-w table.insert(result,"text-align: right; transform: translateX(-100%) "..trf.."; ") else table.insert(result,"text-align: center; transform: translateX(-50%) "..trf.."; ") end table.insert(result,'font-weight: normal; line-height: '..shape.textLH..'; letter-spacing:'..shape.textSP..'; vertical-align: bottom;') end if string.find(shape.textAT or '','bold') then textItem='<b>'..textItem..'</b>' end if string.find(shape.textAT or '','italic') then textItem='<i>'..textItem..'</i>' end if shape.textOL=='0px' and shape.textBG~='transparent' and dotItem==0 then table.insert(result,'\"><span style=\"background-color: '..getColor(shape.textBG)..'; color:inherit;\">'..textItem..'</span></div>') else table.insert(result,"\">"..textItem.."</div>") end if tlink~='' and not string.match(row.param1 or "","nolink") then table.insert(result,makeLinkBox(row.gridx+row.dx+linkoffset, row.gridy+row.dy+bry, 16, row.dotTag..' '..row.title,tlink)) end end local function getshapetable(row,shape) -- Construct CSS divs for a dot from shape and map data local result={} local w,h,r=getsize(shape.shapeSize) local tshape,tlink=checkfortooltip(row.title,row.dx,row.dy,row.dotTag,row.dlink,string.match(row.param1 or "","nolink") ) local align=row.labelPos or '' local offsetx,offsety=0,0 local ty=tonumber(string.match((shape.tagSize or 9),"%d+" )) if row.labelText and row.labelText~='' then ty=tonumber(string.match((shape.textSZ or 11),"%d+" )) else align='center' end -- it is just for dotTag, so justify center -- identify align value and extend offsets local widthzone,heightzone = w/2+4,h/2+4 local theta,r = math.deg( math.atan2(row.dy, row.dx)) , math.sqrt(row.dx^2 + row.dy^2) if align=='auto' or align=='' then if (theta < -112 or theta > 112) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "left" offsetx=1 elseif (theta > -68 and theta < 68) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "right" offsetx=-1 elseif theta <0 then offsety=ty/2-1 -- bottom else offsety=0-ty/2+1 -- top end elseif align=='left' or string.find(align,'west') then row.dx=row.dx - w/2 widthzone=4 offsetx=-1 elseif align=='right' or string.find(align,'east') then row.dx=row.dx + w/2 widthzone=4 offsetx=1 offsety=-ty/2 +1 elseif align=='top' or align=='north' then if string.find(shape.shape,'curve') then row.dy=row.dy-(h/2)-12 heightzone=4 offsety=ty/2-1 else row.dy=row.dy - h-2 heightzone=4 offsety=ty/2-1 end elseif align=='bottom' or align=='south' then if string.find(shape.shape,'curve') then row.dy=row.dy+(h/2) heightzone=4 offsety=ty/2-1 else row.dy=row.dy + h+2 heightzone=4 offsety=0-ty/2-1 end end if r > widthzone and shape.textEW ~= "0px" and not string.match(row.param1 or "","noline") then makeLineTo(result, row.gridx+1, row.gridy-1, row.gridx+row.dx+offsetx, row.gridy+row.dy+offsety, shape.textEW, shape.textES,shape.textEC,1) end if row.posType and row.posType=='mark-line' then if row.gridx2 and row.gridy2 then makeLineTo(result, row.gridx, row.gridy, row.gridx2, row.gridy2, tostring(row.mlWidth)..'px', row.mlStyle, getColor(shape.textEC),row.mlGap) -- debugmsg('makeLineTo line drawn from '..row.code..' with width '..tostring(row.mlWidth)..'px '..row.mlStyle..' and color '..getColor(shape.textEC)) end end if w ~= 0 then if shape.shape=='itriangle' then shape.shape='triangle' shape.shapeSize=tostring(w)..'px'..tostring(w/2)..'px' end if shape.shape=="triangle" then if shape.outlineWidth ~= "0px" then makeTriangle(result,row,shape,true,false) -- larger triangle to give the outline, if required end makeTriangle(result,row,shape,false,tshape) -- smaller triangle to fit over the top elseif shape.shape=="square" or shape.shape=="box" or shape.shape=='panel' then makeSquare(result,row,shape,tshape) elseif shape.shape=="circle" or shape.shape=="ellipse" then makeCircle(result,row,shape,tshape) elseif string.find(shape.shape,'image:')==1 then makeImage(result,row,shape) elseif shape.shape=="rulea" or shape.shape=='rule' then makeRuleA(result,row,shape) elseif shape.shape=="curvea" or shape.shape=="curvec" then makeCurveA(result,row,shape) else -- use a pathshape clipPath if shape.outlineWidth ~= "0px" then makeClipPath(result,row,shape,true,false) -- larger path-shape to give the outline, if required end makeClipPath(result,row,shape,false,tshape) end end if row.ppwidth and row.ppwidth>0 then makePhotoPanel(result,row,shape) end if shape.shape=='panel' and row.labelText then makePanelText(result, row, shape) else if row.dotTag and row.dotTag ~= "" then -- there is a dotTag if (row.dx==0 and row.dy==0) and w>0 then -- it is on the dot so if dotsize is not 0 any label is ignored makeTextItem(result, row, shape, align, tlink, '<b>'..row.dotTag..'</b>', 1) else if row.labelText and row.labelText~='' then -- tag and label both used makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.labelText, 0) else makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.dotTag, 2) -- tag is ouside the dot end end else if (row.labelText and row.labelText~='') then -- just the label. No tag makeTextItem(result, row, shape, align, tlink, row.labelText, 0) end end end if tlink and tlink~='' then table.insert(result,makeLinkBox(row.gridx, row.gridy,w+3,row.dotTag..' '..row.title,tlink)) end return table.concat(result) end local function getmapframecontent(args,use) local result, comma= {}, '' local propertyTable= {} local color=splitItem(string.lower( args['map-data-color'] or 'dark orange'), 2) if use == 'basemap' then table.insert(result, '[') propertyTable.stroke = "#000000" propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') propertyTable['stroke-opacity'] = tonumber(color[2] or '15')/100 else propertyTable.title = args['map-data-text'] or args['map-data'] or '' propertyTable.stroke = getColor(color[1]) propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') propertyTable['stroke-opacity'] = tonumber(color[2] or '100')/100 end if args['map-data-inverse'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geomask', ids = args['map-data-inverse'], properties = {title = (args['map-data-text'] or ''), stroke = '#555555', fill = '#555555', ['fill-opacity'] = 0.1, ['stroke-width'] = 1, ['stroke-opacity'] = 0.5 } } table.insert(result, mapJson) comma=', ' end if args['map-data-heavy'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-heavy'], properties = { title = args['map-data-heavy'], stroke = propertyTable.stroke, ['stroke-width'] = 9, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.25 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data-light'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-light'], properties = { title = args['map-data-light'], stroke = propertyTable.stroke, ['stroke-width'] = 3, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.25 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data'] then propertyTable['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.1 local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data'], properties = propertyTable } table.insert(result, comma .. mapJson) comma=', ' end if args['map-wdqs'] then -- inserts a SPARQL wdqs query to find geopoints, geoshapes etc from wikidata local mapSparql = mw.text.jsonEncode{ type = 'ExternalData', service = args['map-wdqs-type'] or 'geopoint', query = args['map-wdqs'], } table.insert(result, comma .. mapSparql) -- debugmsg('mapSparql = ' .. mapSparql) end if args['map-raw'] then table.insert(result, comma .. args['map-raw']) end if use=='basemap' then table.insert(result,']') end return table.concat(result) end --eg | minilocator=filename,bottom right,132px153px, 38%,60%, 22px local function makeLocatorMap (args, result) local miniFile,pos,itemlist,miniW,miniH, miniX,miniY,miniBox, miniBH if args['mini-locator'] then pos,miniFile=extractItem(args['mini-locator']) -- first item is filename, in quotes if it includes commas itemlist=splitItem(pos,5) -- put items in a table filename removed, position,WpxHpx, x%y%,box pos=itemlist[2] or 'right' miniW,miniH=getsize(itemlist[3]) miniX=tonumber(string.match(itemlist[4] or '0','[%d]+'))*miniW/100 miniY=tonumber(string.match(itemlist[5] or '0','[%d]+'))*miniH/100 miniBox=tonumber(string.match(itemlist[6] or '0','[%d]+')) miniBox=miniBox*miniW/100 miniBH=miniBox * maplist.height/maplist.width elseif args['mini-file'] then miniFile = args['mini-file'] pos=string.lower(args.minimap or 'right') if pos=='file' then pos='right' end miniW,miniH = tonumber(args['mini-width'] or 60), tonumber(args['mini-height'] or 60) -- find top left corner of locator miniBox,miniBH=tonumber(args['minimap-boxwidth'] or '0'),0 -- firm up pos offsets for dot (with % or not) and boxsize if any, miniX, miniY=args['minipog-gx'],args['minipog-gy'] if not miniX then miniX=tonumber(args['minipog-x'] or '0') else miniX=tonumber(miniX)*tonumber(miniW)/100 end if not miniY then miniY=tonumber(args['minipog-y'] or '0') else miniY=tonumber(miniY)*tonumber(miniH)/100 end if args['minipog-gx'] then miniBox=miniBox*miniW/100 end miniBH=miniBox * maplist.height/maplist.width else return end local miniTop,miniLeft=0,1 if not string.find(pos,'top') then --only use top left, as link box is in top right or put bottom left or right miniTop=maplist.height+2-miniH if string.find(pos,'right') then miniTop=miniTop-15 -- to avoid (c) line miniLeft=maplist.width-1-miniW end end table.insert(result,'<div style="position: absolute; outline-width: 1px; outline-style: solid; outline-color: white;') table.insert(result,'top: '..tostring(miniTop)..'px; left:'..tostring(miniLeft)..'px; width:'..tostring(miniW)..'px;' ) table.insert(result,'background-color:transparent; color:inherit;">[[File:'..miniFile..'|'..tostring(miniW)..'px|File:'..miniFile..']]</div>') if miniX and miniX>0 then if args['minipog-y'] then miniY=miniY/1.04 end if args['minipog-x'] then miniX=miniX/1.04 end if miniBox<1 then table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-3-6)..'px; left:'..tostring(miniX+miniLeft-3)..'px;') table.insert(result, 'width: 6px; background-color:transparent; color:inherit;">[[File:Red pog.svg|6px|link=]]</div>') else table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-miniBH/2)..'px; left:'..tostring(miniX+miniLeft-miniBox/2)..'px;') table.insert(result, 'width: '..tostring(miniBox)..'px; height:'..tostring(miniBH)..'px; outline:1px solid #AA1205; background-color:#AA120522; color:inherit;"></div>') end end end local function makeArcText(args,result,nval) local items, itemlist='',{} local arcText='' if args['arc'..nval] then items=convertCoords (args['arc'..nval]) items,arcText=extractItem(items) -- first item is text, in quotes if it includes commas itemlist=splitItem(items,9) -- put items in a table: text, lat,lon,size,color,angle,gap,radius,ellipse if itemlist[4]=='' then itemlist[4]='12' end itemlist[4]=string.match((itemlist[4] or '12'),"[%.%-?%d]+") if itemlist[5]=='' then itemlist[5]='grey' end itemlist[6]=string.match((itemlist[6] or '0'),"[%.%-?%d]+") itemlist[7]=string.match((itemlist[7] or '0'),"[%.%-?%d]+") itemlist[8]=string.match((itemlist[8] or '0'),"[%.%-?%d]+") end if args['arc-coord'..nval] then local itemTab=splitItem(convertCoordsTrad (args['arc-coord'..nval]),2) maplist.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) maplist.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) else maplist.lat=tonumber(string.match(args['arc-lat'..nval] or itemlist[2] or '0',"[%.%-?%d]+")) maplist.lon=tonumber(string.match(args['arc-lon'..nval] or itemlist[3] or '0',"[%.%-?%d]+")) end local arcX,arcY=maptogrid(maplist,6) arcText = args['arc-text'..nval] or arcText local fontSize =tonumber(args['arc-text-size'..nval] or itemlist[4] or '12') local textColor=getColor(string.gsub(args['arc-text-color'..nval] or itemlist[5] or 'grey','[%s]+','') ) local arcAngle= tonumber((args['arc-angle'..nval]) or (itemlist[6]) or '45')-90 local arcRadius =tonumber(args['arc-radius'..nval] or itemlist[8] or '0.05') local arcGap = tonumber(args['arc-gap'..nval] or itemlist[7] or '1')* ( ( math.sin(8-math.rad(arcRadius))^8 )+0.4 )*( ( fontSize+6 )/15 ) arcRadius=450*arcRadius*0.75 local ellipseFactor=tonumber(args['ellipse-factor'..nval] or itemlist[9] or '1') local arcRotate =arcAngle+90 if arcGap<0 then arcRotate=arcAngle-90 end local latF=arcY - fontSize + (0-(math.sin(math.rad(arcAngle))) * arcRadius) local lonF=arcX - fontSize +(0-(math.cos(math.rad(arcAngle))) * arcRadius) local step=1 for codepoint in mw.ustring.gcodepoint( arcText ) do -- block step=1,#arcText do table.insert(result,'<div style="position: absolute;') local posY=tostring(round( (latF + (math.sin(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius)) *ellipseFactor,2))..'px;' local posX=tostring(round( (lonF + (math.cos(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius))/ellipseFactor,2))..'px;' table.insert(result,' top: '..posY..' left: '..posX..' transform: rotate( '..tostring(round(arcRotate +((step-1)*arcGap)),2)..'deg);') table.insert(result,'width:'..tostring(fontSize*2)..'px; text-align: center; background-color:transparent; color: '..textColor..';') table.insert(result,'vertical-align: baseline; font-size: '..tostring(fontSize)..'px;">'..mw.ustring.char(codepoint)..'</div>') step=step+1 end end local function makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) local item={} itemdescription=stripdivs(itemdescription or '') local templon=lon if lon > 180 then templon=lon-360 end --for hemisphere+ or -1 dots if lon < -180 then templon=lon+360 end -- use 'real' coordinates for geohack label, while retaining shifted coords for plot if itemcolor=='transparent' then itemcolor='white' end itemcolor=getColor(itemcolor) -- ensure no opacity, which breaks maplink if string.find(itemcolor,'#')==1 and #itemcolor>7 then itemcolor=string.sub(itemcolor,1,7) end table.insert(item, '{ "type": "Feature", "properties": {') table.insert(item, ' "title": "'..itemtitle..'",') table.insert(item, ' "description": "'..itemdescription) table.insert(item, ' ([https://geohack.toolforge.org/geohack.php?params='..tostring(lat)..';'..tostring(templon)) table.insert(item, '_dim:2000 '..tostring(lat)..','..tostring(templon)..'])",') table.insert(item, ' "marker-symbol": "-number-'..string.gsub(group,'%W','')..'", "marker-size": "medium", "marker-color": "'..itemcolor..'" },') table.insert(item, ' "geometry": {"type": "Point", "coordinates": ['..tostring(lon)..','..tostring(lat)..'] } }') return table.concat(item) end local function makeLegendBox(result,args) local legend ={} local line,count, maxWidth='',1,8 local item line,item=extractItem(args.legendBox or '') local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end legend.Text = a -- convert hats to line breaks line=splitItem(line,6) -- (text, size, poition,background color, text/outline color, param options) legend.Size=line[2] or '150px80px1px' legend.Pos=line[3] or '10px10px' legend.Background=line[4] or 'beigeground' legend.Color=line[5] or 'darkbrown' legend.Param= line[6] or '' local argnum,legendCount,titleHeight='1',1,0 if (legend.Text and legend.Text~='') then titleHeight=15+(13.4*(select(2, string.gsub(legend.Text,"<br>", "")))) end local legendLine,legendGroup,legendY={},{},{} while args['legendItem'..argnum] do -- assign legendLine, legendGroup, legendY for each dot line,legendLine[legendCount] = extractItem(args['legendItem'..argnum] or '') line=splitItem(line,3) legendGroup[legendCount]=line[2] or argnum if shapeList[legendGroup[legendCount]] then maxWidth=math.max(tonumber(string.match(shapeList[legendGroup[legendCount]].shapeSize or '10','[%d]+')),maxWidth) else maxWidth=math.max(tonumber(string.match(shapeList['1'].shapeSize or '10','[%d]+')),maxWidth) end maxWidth=maxWidth+1 if line[3] then legendY[legendCount]=tonumber(string.match(line[3],'[%d]+')) else legendY[legendCount] = 3+maxWidth*(legendCount-1)+titleHeight -- if (legend.Text and legend.Text~='') then legendY[legendCount]=legendY[legendCount]+15 end end legendCount=legendCount+1 argnum=tostring(legendCount) end local w,h,r=getsize(legend.Size or '') local x,y=getsize(legend.Pos or '') local div=mw.html.create ('div') div:css('position', 'absolute') div:css('outline', '1px solid'..getColor(legend.Color)) if r~=0 then div:css('border-radius',tostring(r).."px") end div :css('top', y.."px") :css('left', x.."px") :css('width', w.."px") :css('height', h.."px") :css('line-height','105%') :css('background-color', getColor(legend.Background) ) :css('color','inherit') if not string.find(legend.Param,'noshadow') then div:css('box-shadow', '2px 2px 4px #33203335') end div:tag( 'div' ) :css('position', 'absolute') :css('top','1px') :css('left', (w/2).."px") :css('width',(w-8)..'px') :css('text-align', 'center') :css('color', getColor(legend.Color)) :css('transform', 'translateX(-50%)') :css('font-size','11px') :wikitext(legend.Text) :done() for lct=1,legendCount-1 do --local t=legendGroup[lct] local shape=shapeList[legendGroup[lct]] or shapeList['1'] local row={} row.gridx=3+maxWidth/2 row.gridy=(legendY[lct] or 0) + 5 if shape.shape=='image:' then row.gridy=row.gridy+6 end row.dx=0 row.dy=0 local legendShape= getshapetable(row,shape) div:wikitext(legendShape) :tag( 'div' ) :css('position', 'absolute') :css('top',(legendY[lct] or 0)..'px') :css('left', (maxWidth+6)..'px') :css('width', (w-maxWidth-6)..'px') :css('text-align', 'left') :css('line-height','103%') :css('color', getColor(legend.Color)) :css('font-size','10px') :wikitext(legendLine[lct]) :done() end div:allDone() table.insert(result,tostring(div)) end function p._main ( args ) local result={} local frame=mw.getCurrentFrame() local dotTable={} local magVal,scaleVal = args.magnify,'' local origH,origW=maplist.height,maplist.width if magVal then --set up the values needed to magnify the top-right portion of the map magVal=tonumber(string.match(magVal or '0',"[%.%-?%d]+")) or 1 if magVal>1 and magVal <=2 then maplist.height= round(maplist.height/magVal,0) maplist.width=round(maplist.width/magVal,0) scaleVal='transform: scale('..magVal..') translateY('..tostring((origH-maplist.height)/2)..'px);' else magVal=1 end --debugmsg('magVal='..magVal..', Magnify: H='..maplist.height..', ('..origH..'), W='..maplist.width..', ('..origW..'), scale='..scaleVal) --debugmsg('top: '..tostring(((origW-maplist.width))/2)..'px;right: '..tostring(0-((origW-maplist.width))/2)..'px; (alt top: '..tostring(((origH-maplist.height))/2)..'px;)') end -- set up the three nested div boxes (plus an extra if centered) to put the map plus title/caption area, in an appropriate frame on the page if args.float=='center' or args.float=='centre' then table.insert(result,'<div class="center"><div class="thumb tnone">') elseif args.float=='left' then table.insert(result,'<div class="thumb tleft">') else table.insert(result,'<div class="thumb tright">') end table.insert(result,'<div class="thumbinner" style="position: relative; top: 0px; right: 0px; width: '..(args.width or "400")..'px;">') if args.title then table.insert(result,'<div class="center" style="font-weight:bold">'..args.title..'</div>') end if magVal and magVal >1 and magVal<=2 then table.insert(result,'<div class="thumbinner noresize" style="display:block; position: relative; outline:0px; border:0px; padding:0px; background-color:transparent; color:inherit;') table.insert(result,'top: 50%; right: '..tostring(0-((origW-maplist.width))/2)..'px; ') table.insert(result,'height: '..origH..'px; width: '..maplist.width..'px; '..scaleVal..'">') else magVal=1 table.insert(result,'<div class="thumbinner noresize" style="position: relative; outline:0px; border:0px; padding:0px;') table.insert(result,'top: 0px; right: 0px; ') table.insert(result,'height: '..maplist.height..'px; width: '..maplist.width..'px ">') end -- Create the basemap using mapframe local mapframecontent=getmapframecontent(args,'basemap') table.insert(result, frame:extensionTag{ name ='mapframe', content=mapframecontent, args={width=tostring(maplist.width), height=tostring(maplist.height), zoom=tostring(maplist.zoom), longitude=tostring(maplist.lonbase), latitude=tostring(maplist.latbase), mapstyle=maplist.mapstyle, frameless=true } } ) --Add coverall box to block the unhelpful links from mapframe - which wouldn't include all the dots. Reinstate some links to osm and wikimedia table.insert(result,'<div style="position: absolute;width:'..tostring(maplist.width)..'px; height:'..tostring(maplist.height)..'px;') table.insert(result,'top:0px;left:0px;background-color:#FFFFFF00; color:inherit;"></div>') --Add replacent hover-links for OpenStreetMap and maps terms and conditions table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-13)..'px; width: 12px; height: 12px">') table.insert(result,'[[file:Transparent.svg|12px|link=https://www.openstreetmap.org/copyright|'..aboutOSM..']]</div>') table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-110)..'px;') table.insert(result,'width: 12px; height: 12px background-color: transparent; color:inherit;">') table.insert(result,'[[file:Transparent.svg|12px| link=https://foundation.wikimedia.org/wiki/Policy:Maps_Terms_of_Use|'..termsOfUse..']]</div>') -- Add scale-line if not args.scalemark or args.scalemark~='0' then local top=maplist.height-42 local left=maplist.width-61-(tonumber(args.scalemark or '1')) local minipos=string.lower(args.minimap or '') -- scalemark gets pushed left if it would be behind the minimap if minipos~='' and not(string.find(minipos,'left') or string.find(minipos,'top') ) then local offset=tonumber(args.scalemark or '1')-tonumber(args['mini-width'] or '60') if offset<1 then left=maplist.width-61-tonumber(args['mini-width'] or '60') end end if maplist.width-left >216 then top=top+14 end -- shunt scaleline down if it is beyond the copyright stuff local scalek,scalem=getScale(maplist.zoom, maplist.latbase, magVal) local magReduce='' table.insert(result,"<div style=\"display:inline-block; position: absolute; background-color: #111111") table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;M0,8 l0,4 l20,0 l0,-4 l-0.3,0 l0,3.7 l-19.4,0 l0,-3.7 z&#39;) ") table.insert(result,"; width:20px") -- path is a 20px20px box, and then rescales if magVal==1 then table.insert(result,"; top:"..tostring(top-1).."px") table.insert(result,"; left:"..tostring(left+16).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5)..", "..tostring(1.5)..")") else -- shrink the scalemark to compensate for magnification table.insert(result,"; top:"..tostring(top-(magVal*0.28)).."px") table.insert(result,"; left:"..tostring(left+(16*(magVal*1.15))).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5*(1/magVal))..", "..tostring(1.5*(1/magVal))..")") magReduce= 'scale('..tostring(1/magVal)..')' end table.insert(result,"\"></div>") table.insert(result,'<div style="position: absolute; top: '..tostring(top)..'px; left: '..tostring(left+47)..'px; font-size: 9.5px; line-height: 126%; width: fit-content;') table.insert(result,'color: #444433; background-color: transparent; text-align: right; transform: '..magReduce..' translateX(-100%);">'..scalek..'<br>'..scalem..'</div>') end --Set up the shapeList and dotList tables, to provide data to go on the map local sgNumbers,sgSortable={},{} --s1,s2 sgNumbers["1"]="1" if args.useFormatStyle and args.useFormatStyle=='shortstyle' then shapeList=ParseShapeTypes (shapeList,args,"1") for argindex,argv in pairs(args) do -- build a list of all the numbered sg's that have been used if string.find(argindex,"sg[a-f,n%d]+") == 1 then -- only look through the sga,sgb, sgc,sgd,sge,sgf and sgn args local x=string.match(argindex,"[%d]+") -- find its number if x and not sgNumbers[x] then sgNumbers[x]=x end -- only add if not already found end if string.find(argindex,"sg[a-f]H") == 1 and highlightNum then highlightOption=true end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form for k,v in pairs(sgSortable) do -- work through the sorted list, parsing each set of sg's in turn, from 1 upwards shapeList=ParseShapeTypes (shapeList,args,v) end if highlightOption==true then shapeList=ParseShapeTypes (shapeList,args,'H') end end local dotList,dotresult,dotItemTable,dotGroupList={},{},{},{} if (not args.useFormatStyle) or args.useFormatStyle=='standardstyle' then local dotmax=0 for indx in pairs(args) do if string.match(indx,'mark%-coord[%d]+') or string.match(indx,'lat[%d]+') then dotmax=math.max(dotmax, tonumber(string.match(indx,"[%d]+"))) end if (indx=='shapeH') or (indx=='shape-colorH') or (indx=='shape-outlineH') or (indx=='label-colorH') then highlightOption=true end end dotItemTable=tradstyleParseShapes(args,dotItemTable,dotmax) for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) then sgNumbers[x]=x -- add it to the list end end else for indx in pairs(args) do if string.match(indx,'dot[%d]+') then table.insert(dotList,indx) end --add the index name for dot1, dot2 etc to dotList end table.sort(dotList,morethan) for indx,dotName in pairs(dotList) do dotresult=ParseData(args,string.match(dotName,'[%d]+') ) -- using each dot number, assign the settings for each dot to a dotresult item line table.insert(dotItemTable,dotresult) -- and store that item line within the dotItemTable end end for arcVal = 65,91 do -- check through args looking for any arcs local arcLetter=string.char(arcVal) if args['arc-text'..arcLetter] or args['arc'..arcLetter] then makeArcText(args,result,arcLetter) end end local dotdivs='' local ddots=0 if dotItemTable[1] then local ddots=(dotItemTable[1].lat or 0)+(dotItemTable[1].lon or 0) end local fgroup='F'..tostring(maplist.latbase+maplist.lonbase+ddots ) local FullscreenList={} local addcomma='' for i,dotitem in pairs(dotItemTable) do -- working throug each dot item, merge the dot and shape values into a full set of css text local dotgroup= dotitem.group or "0" if dotitem.posType=='mark-line' and dotitem.markDest then --find destination xy values for any mark-lines for n,v in pairs(dotItemTable) do if v.code == dotitem.markDest then dotitem.gridx2=v.gridx dotitem.gridy2=v.gridy break end end end local qtype=dotitem.group -- find which shape group each dot has been assigned --debugmsg('dotgroup='..qtype..', sg='..(sgNumbers[qtype] or 'nil')..' , shapeList='..shapeList[qtype].shape) if not sgNumbers[qtype] then qtype="0" end --shapeList[dotitem.group] will give access to the shape values for that dot if highlightNum==dotitem.code and highlightOption==true and shapeList['H'] then table.insert(result, getshapetable(dotitem,shapeList['H'])) else table.insert(result, getshapetable(dotitem,shapeList[qtype])) -- Add the actual css instructions for each dot end if shapeList[dotgroup] and not string.find((dotitem.param1 or ''),'nomap') then -- only add if not excluded with 'nomap' labelText local ftext='' if dotitem.dotTag~='' and not string.match(dotitem.labelText or '','[%d]') then ftext=stripdivs(dotitem.dotTag or '')..' <br>' end if (dotitem.labelText ~= ftext) and dotitem.dotLink =='' then ftext=ftext..' '..stripdivs(dotitem.labelText)..'<br>' end if (dotitem.dotLink) and (dotitem.dotLink ~='') then ftext=ftext..dotitem.dotLink..'<br>' end if dotitem.imageName then ftext=ftext..'[[File:'..dotitem.imageName..'|250px]]' end table.insert(FullscreenList,1, makeFullscreenItem (string.gsub(ftext,"[\n]+"," "), dotitem.info,dotitem.lat,dotitem.lon,fgroup,shapeList[dotgroup].shapeColor)..addcomma ) addcomma=', ' end -- makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) -- Always add to start of list, to reverse the sequence, and separate with commas except for first item, which is now at the end end if args.legendBox then makeLegendBox(result,args) end if args.minimap or args['mini-locator'] then makeLocatorMap(args,result) end -- add tag link and details for fullscreen version addcomma='' if (mapframecontent or '[]') ~= '[]' then addcomma=',' end mapframecontent=getmapframecontent(args,'fullscreen') local contentstart='[ '..mapframecontent..addcomma..'{ "type": "FeatureCollection", "features": [ ' --extra features after first square bracket local contentend=' ] } ]' table.insert(result, '<div style="position: absolute;top: 9px;left: '..tostring(maplist.width-34)..'px">') table.insert(result, '<div style="color: white; opacity:100; font-size: 19px; font-weight:normal; text-align: left;">') table.insert(result, frame:extensionTag{ name ='maplink', content=contentstart..table.concat(FullscreenList)..contentend, args={zoom=tostring(maplist.zoom+1), class='no-icon', frameless='1', latitude=tostring(maplist.latbase), longitude=tostring(maplist.lonbase), --add invisble 'en-spaces' for tooltip text='<div title="'..fullscreenlinktext..'">&nbsp;&nbsp;&nbsp;</div>'} } ) table.insert(result,'</div></div>') -- end of maplink ----- -- add closing div for main map table.insert(result,'</div>') -- collate caption material to go in the outer div class local autocaption=string.lower(args['auto-caption'] or 'no') local autoOff=autocaption:match("(%w+)(.*)") -- select the first word in autocaption and see if it is a negativeAnswer) if args.caption or not negativeAnswer[autoOff] then table.insert(result,'<div class="thumbcaption" style="text-align:left">') if args.caption then table.insert(result,args.caption) end end local columns=tonumber(autoOff:match("[%d]+") or '1') if columns>1 then columns=round(maplist.width/(columns*17), 0) end -- convert from em to px for historical reasons --for k in pairs(dotList) do capchk=capchk..(args["dotlink"..k] or '') end local capchk=nil local captionList = {} for key, value in pairs(dotItemTable) do -- only add an autocaption line if there is both a dotTag and a dotLink line available and it is not marked as nolist if value.dotTag and value.dotTag~='' and (not string.find(value.param1 or '','nolist')) and string.gsub(value.dotLink or '',"%s+","")~='' then table.insert(captionList, {key = key, value = value}) capchk=true end end if capchk and (not negativeAnswer[autoOff]) then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) local myDivision = string.gsub((args.toggletext or toggletext), "%s+", "") if string.find(autocaption,'collaps') then table.insert(result,'<hr><div class="mw-customtoggle-'..myDivision..'">'..(args.toggletext or toggletext)..'</div>') if string.find(autocaption,'collapsed') then table.insert(result,'<div class="mw-collapsible mw-collapsed" id="mw-customcollapsible-'..myDivision..'">') else table.insert(result,'<div class="mw-collapsible" id="mw-customcollapsible-'..myDivision..'">') end end if string.find(autocaption, 'columns=') then columns=string.match(autocaption,'[%d]+',string.find(autocaption, 'columns=') ) end table.insert(result,'<div class="mw-collapsible-content" style="column-count:'..columns..'; column-rule:solid 1px;text-align:left;padding-top:5px">') table.sort(dotList,lessthan) local nval,ngrp='','0' for k,v in pairs(captionList) do nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag ngrp=v.value.group or '0' if v.value.dotLink and v.value.dotLink~='' and nval and nval~='' then local c1,c2 if nval==args.highlight then c1,c2=checkColors(shapeList['H'].shapeColor) else c1,c2=checkColors(shapeList[ngrp].shapeColor) end table.insert(result,'<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;') table.insert(result,'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div> '..v.value.dotLink..'<br>') end end table.insert(result,'</div>') -- end for caption-content div if string.find(autocaption,'collaps') then table.insert(result,'</div>') end -- end for toggle frame end if args.caption or not negativeAnswer[autoOff] then table.insert(result,'</div>') end -- end for whole caption frame table.insert(result,'</div></div>') -- outer two frames if args.float == 'center' or args.float=='centre' then table.insert(result,'</div>') end if args['show-new-format'] == 'hints' then -- provide a 'format hint panel' in the 'Preview Box' local w="<small>Below are some template hints for the 'sga' compressed version, " w=w..'{{tl|OSM Location dots}}. It can use these, instead of the more verbose {{tl|OSM Location map}} parameter format. ' w=w..'Data is divided between a "ShapeGroup" and the "Dots", so that a single shapeGroup can be used for multiple dots on the map. ' previewMsg(w..'(nb. the "group" value can be the number or an assigned name of a shapeGroup)</small>') w='<small>{{tl|OSM Location dots}}: | dot(n)=group,lat,lon,dotTag | ' w=w..'dotlink=link/tooltip | dotlabel=label,position,dx,dy,param1,info | dotpic=filename <br>' w=w.."(nb. param1 options include 'nolink' 'nolist' 'nomap' 'hemisphere-1' 'hemisphere+1', 'noline' - quotes not required, separate with spaces).<br>" w=w..'| sga = Shape,Sizepx,Color,Angledeg | sgb= OutlineWidth,Color,Style | sgc=TextSize,Color,Angle,bold italic <br>' w=w..'| sgd=TagSizepx,Color,Spacer,Angledeg | sge=LineWidth,Color,Style | sgf=TextSpacingpx,LineHeight%,Outlinepx,backgroundColor<br>' w=w..'| sgn=Name (optional, to assign a meaningful name) | sgp=Parent (can be the name or number of the parent shapeGroup. ' previewMsg(w..'Each shapegroup will inherit values from a parent, stretching back to "sga1" and its default values.)</small>') if #pmsg then local dbg={} for i,x in pairs(pmsg) do table.insert(dbg,x..'<br>') end dbg=mw.addWarning(table.concat(dbg)) table.insert(result, dbg) end end if args.coordtest then debugmsg(mw.text.nowiki(args.coordtest)) end for i,x in pairs(msg) do table.insert(result, x..'<br>') end return table.concat(result) end function p.main(frame) local args = getArgs(frame) local itemTab={} maplist.width=tonumber(args.width) or 400 maplist.height=tonumber(args.height) or 300 if args.coord then itemTab=splitItem(convertCoordsTrad (args.coord),2) maplist.latbase=itemTab[1] maplist.lonbase=itemTab[2] else maplist.lonbase=tonumber(args.lon) or 5 maplist.latbase=tonumber(args.lat) or 0 end maplist.zoom=tonumber(args.zoom) or 1 visibleLinks=args.showlinks highlightNum=args.highlight if args.nolabels=='1' then maplist.mapstyle='osm' else maplist.mapstyle='osm-intl' end return p._main(args) end return p 690638pnzsj710x250v5qs9jtm9s5dr 204464 204463 2026-07-05T15:26:39Z HaxelKaszëba 17492 204464 Scribunto text/plain require('strict') local delink=require('Module:Delink').delink local getArgs = require('Module:Arguments').getArgs local p = {} local maplist={} local sgNames={} local highlightOption=false local highlightNum local visibleLinks -- This module creates framed maps of anywhere in the world, at the required scale, and enables annotations, -- dots, shapes, lines and other ways to customise the area of the map being shown. It also provides a link -- to an interactive fullscreen version, which has locator dots instead of annotations and shapes. -- This is the 2025 successor module to a wiki-markup template version of 2024, which itself was a successor -- to the 'Graph'/VEGA driven template that was begun in 2016, until the Vega version was switched off in 2023. -- This module is called from template {{OSM Location map}}, which uses the same parameter formats as before. -- In addition it will be possible to use a more concise parameter format using the template {{OSM Location dots}} -- In general the css output from the two formats will be identical, but the the concise version will allow bits of -- greater control over some of the settings. -- see the documentation on the two template pages for details of how to use the mapping features. -- If language customisation is needed, there are text items below that can be translated. Also see the color table -- below with details of how to add additional color names to allow localised alternatives. -- (Translating other language shape-types could be possible, but has not currently been contemplated. -- Parameter name translation would be harder but likely to be possible, ideally still retaining compatibility -- with template calls already written using English). local negativeAnswer={no=1,'0'-1,off=1} local fullscreenlinktext='Click for interactive fullscreen map with links to nearby articles' local toggletext='[Hide/show caption list]' local termsOfUse='Maps: terms of use' local aboutOSM='About OpenStreetMaps' local shapeList={} --This sets up the 'factoryDefault' shape group 0 shapeList["0"]={shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border of the shape outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items text settings for labels textSZ="11px", textCL="darkgrey", textNG="0deg", --sgf further text settings textSP="0px", textLH="120%", textOL="0px", textBG="transparent", --sgd items for dotTag text settings tagSize="10px", tagColor="white", tagSpacer="0px", tagAngle="0deg", --sge items for extension line to connect label to dot textEW="0px", textEC="darkgrey", textES="solid" } local colorList={} -- used by colorLookup to catch unsupported colors (eg 'LimeGreen'), to convert to generic version colorList['green']='hardgreen' -- it could also be added to to include alternative language equivelants, for a quick solution. colorList['red']='hardred' -- colorList ['source'] = target colorList['white']='white' -- converts any color that includes 'source' into its equivelent target colorList['blue']='hardblue' -- note, for translation you can add to this list, rather than replace it, colorList['brown']='brown' -- which would mean existing map definitions in english would also still work, alongside translated ones colorList['grey']='hardgrey' colorList['gray']='hardgrey' colorList['purple']='hardpurple' colorList['orange']='hardorange' colorList['leaf']='hardleaf' --for a more thorough translation, you can add all the variants of the colors as further CTB elements and hex values or redirects local CTB={} -- set up a table of color names (the CTB Color table index) and html hash colorhex values. CTB["paleblue"],CTB["softblue"],CTB["hardblue"],CTB["darkblue"]="#D6E1EC","#77A1CB","#4B77D6","#1c559e" CTB["palered"],CTB["softred"],CTB["hardred"],CTB["darkred"] = "#FCC6C0","#EC644B","#DB3123","#AA1205" CTB["palegreen"],CTB["softgreen"],CTB["hardgreen"],CTB["darkgreen"]= "#D2F0E5","#81AF81","#269F46","#0b7527" CTB["paleleaf"],CTB["softleaf"],CTB["hardleaf"],CTB["darkleaf"]= "#dff5c1","#b5e376","#8cc244","#679c21" CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"]= "#E8E8D6","#AAAA88","#777755","#444433" CTB["palegray"],CTB["softgray"],CTB["hardgray"],CTB["darkgray"]=CTB["palegrey"],CTB["softgrey"],CTB["hardgrey"],CTB["darkgrey"] CTB["palebrown"],CTB["softbrown"],CTB["hardbrown"],CTB["darkbrown"]="#FAF6ED","#CCB56C","#AD7F14","#754910" CTB["palepurple"],CTB["softpurple"],CTB["hardpurple"],CTB["darkpurple"]="#e0d1e6","#c784e0","#a029cf","#7a05a8" CTB["paleorange"],CTB["softorange"],CTB["hardorange"],CTB["darkorange"]="#ffedc2","#ffcf61","#EEB533","#e39f05" CTB["black"],CTB["white"],CTB["yellow"]="#000000","#FFFFFF","#FAF039" CTB["background"],CTB["paleground"],CTB["beigeground"]="#f9f6f2","#FEFEFA","#F5F5DC" CTB["beige"]=CTB["beigeground"] CTB["aqua"],CTB["teal"],CTB["fuchsia"] = "#00FFFF","#008080","#FF00FF" CTB["maroon"],CTB["olive"],CTB["navy"] = "#800000","#808000","#000080" CTB["lime"],CTB["limegreen"],CTB["aquamarine"] = "#00FF00","#32CD32","#7FFFD4" CTB["silver"],CTB["yellow"],CTB["orchid"] = "#800000","#FFFF00","#DA70D6" -- set up a table of predefined clip-paths local pathshape={} pathshape.squaredd = "M 19,1.25 l 0,18 -18,0 0,-18 18,0m-1,1 -16,0 0,16 16,0 0,-16m-1,1 0,14 -14,0 0,-14 14,0zm-1,1 -12,0 0,12 12,0 0,-12zm-1,1 0,10 -10,0 0,-10 10,0z" pathshape.squared = "M 18,2.5 l 0,15 -15,0 0,-15 15,0m-1,1 -13,0 0,13 13,0 0,-13zm-1,1 0,11 -11,0 0,-11 11,0z" pathshape.triangledd="M 0 20,20 20,10 0,0 20ZM1.5 19,10 1.7,18.5 19,1.5 19ZM3 18,17 18,10 3.8,3 18ZM4.5 17,10 5.4,15.4 17, 4.5,17ZM6 16,13.8 16,10 7.4z" pathshape.triangled ="M1,18 l 18,0 l -9,-18 l -9,18zm1.7,-1.1 l 7.3,-14.6 l 7.3,14.6 l -14.6, 0zm1.7,-1 l 11.0,0 l -5.5,-11 l -5.5,11z" pathshape.circledd = "M0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm0.8,0a9.2,9.2 0 1,1 18.4,0a9.2,9.2 0 1,1 -18.4,0m1,0a8.2,8.2 0 1,0 16.4,0a8.2,8.2 0 1,0 -16.4,0zm0.8,0a7.2,7.2 0 1,1 14.8,0a7.2,7.2 0 1,1 -14.8,0m1,0 a6.4,6.4 0 1,1 12.8,0a6.4,6.4 0 1,1 -12.8,0z" pathshape.circled = "M2.5,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0m0.8,0 a5,5 0 1,1 11.4,0a5,5 0 1,1 -11.4,0" pathshape.diamond = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10z" pathshape.diamondd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm1,0 l 6,8.5 l 6,-8.5 -6,-8.5 -6,8.5zm1,0 l 5,-7 5,7 -5,7 -5,-7z" pathshape.diamonddd = "M3,10 l 7,-10 l 7,10 -7,10 -7,-10zm0.75,0 l 6.25,9 l 6.25,-9 -6.25,-9 -6.25,9zm0.75,0 l 5.5,-8 5.5,8 -5.5,8 -5.5,-8zm0.75,0 l 4.75,7 l 4.75,-7 -4.75,-7 -4.75,7zm0.75,0 l 4,-6 4,6 -4,6 -4,-6z" pathshape.crossd = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0zM2.3,10a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.cross = "M3.1,12.5 l4.2,0 l0,4.2 l5,0 l0,-4 l4.2,0 l0,-5 l-4.2,0 l0,-4.2 l-5,0 l0,4.2 l-4.2,0z" pathshape.thincross = "M2,12 l6,0 l0,6 l4,0 l0,-6 l6,0 l0,-4 l-6,0 l0,-6 l-4,0 l0,6 l-6,0z" pathshape.fivepointstar = "M10 0 L12.245 6.91 19.511 6.91 13.633 11.18 15.878 18.09 10 13.82 4.122 18.09 6.367 11.18 0.489 6.91 7.755 6.91Z" pathshape.fivepointstard = "M10 1.5 L 11.90825 7.3735 18.08435 7.3735 13.08805 11.003 14.9963 16.8765 10 13.247 5.0037 16.8765 6.91195 11.003 1.91565 7.3735 8.09175 7.3735 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sixpointstar = "M10 0 L12.323 5.977 18.66 5 14.645 10 18.66 15 12.323 14.023 10 20 7.677 14.023 1.34 15 5.355 10 1.34 5 7.677 5.977Z" pathshape.sixpointstard = "M10 1.5 L 11.97455 6.58045 17.361 5.75 13.94825 10 17.361 14.25 11.97455 13.41955 10 18.5 8.02545 13.41955 2.639 14.25 6.05175 10 2.639 5.75 8.02545 6.58045 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.sevenpointstar = "M10 0 L12.048 5.747 17.818 3.765 14.602 8.95 19.749 12.225 13.69 12.943 14.339 19.01 10 14.72 5.661 19.01 6.31 12.943 0.251 12.225 5.398 8.95 2.182 3.765 7.952 5.747Z" pathshape.sevenpointstard = "M10 1.5 L11.7408 6.38495 16.6453 4.70025 13.9117 9.1075 18.28665 11.89125 13.1365 12.50155 13.68815 17.6585 10 14.012 6.31185 17.6585 6.8635 12.50155 1.71335 11.89125 6.0883 9.1075 3.3547 4.70025 8.2592 6.38495 ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.eightpointstar = "M10 0 L11.88 5.46 17.071 2.929 14.54 8.12 20 10 14.54 11.88 17.071 17.071 11.88 14.54 10 20 8.12 14.54 2.929 17.071 5.46 11.88 0 10 5.46 8.12 2.929 2.929 8.12 5.46Z" pathshape.eightpointstard = "M10 0 L10 1.5 L11.598 6.141 16.01035 3.98965 13.859 8.402 18.5 10 13.859 11.598 16.01035 16.01035 11.598 13.859 10 18.5 8.402 13.859 3.98965 16.01035 6.141 11.598 1.5 10 6.141 8.402 3.98965 3.98965 8.402 6.141ZM0,10a10,10 0 1,0 20,0a10,10 0 1,0 -20,0zm1.5,0a8.5,8.5 0 1,1 17,0a8.5,8.5 0 1,1 -17,0z" pathshape.ring="M2.6,9.5a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0zm1,0a6.5,6.5 0 1,1 13,0a6.5,6.5 0 1,1 -13,0z" pathshape.boxd=pathshape.squared pathshape.boxdd=pathshape.squaredd pathshape.ellipsed=pathshape.circled pathshape.ellipsedd=pathshape.circledd local msg={} local function debugmsg(txt) table.insert(msg,txt) end local pmsg={} local function previewMsg(txt) table.insert(pmsg,txt) end local function colorLookup(color) for c,l in pairs(colorList) do if string.find(color,c) then return l end end return color end local function getColor (color,chk) local c local opacity="100" if not color or color=='' then color='hardgrey' end if color=="transparent" then return color end if color=="background1" then color='background' end if string.byte(color,1,1)==35 and (#color == 7 or #color == 9) then c=color elseif string.byte(color,1,1)==35 and #color == 4 then c=string.sub(color,1,2)..'f'..string.sub(color,3,3)..'f'..string.sub(color,4,4)..'f' else local s=color..'1' s= s:sub(0,s:find("%d")-1) opacity=string.match(color,"%d+") if not CTB[s] then s= colorList[s] end -- check for synonyms and translations if not CTB[s] then debugmsg(mw.addWarning('color = '..color..'. The color name is not defined. Used default grey instead')) end c=CTB[s] or CTB.hardgrey end if opacity and (tonumber(opacity) < 100) and string.find(c,"#")==1 and string.len(c)==7 and opacity~="" then local hexval=string.format("%x",(math.floor(tonumber(opacity)*2.55))) c=c..hexval end return c end function p.colorvalue(frame) -- enable external access to the CTB colorTable values. usage: {{#invoke:OSM Location map|colorvalue|color=hard blue}} local c if not frame.args.color or frame.args.color=='' then c='grey' else c=string.lower(string.gsub(frame.args.color,'%s+','')) end return string.upper(string.sub(getColor(c),2)) end local function checkColors(color) local c=getColor(color,'check') local opacity =1 -- calculate colour brightness and return black or white for contrast if c=='transparent' then return c,'#000000',0 end if not (string.find(c,'#')==1) then return c,'#FFFFFF',0 end if #c>8 then opacity= tonumber('0x'..(string.sub(c,8,9)))/500 end local r=tonumber('0x'..(string.sub(c,2,3)))/255 local g=tonumber('0x'..(string.sub(c,4,5)))/255 local b=tonumber('0x'..(string.sub(c,6,7)))/255 if 0.2126 * r + 0.7152 * g + 0.0722 * b / opacity < 0.7 then return c,'#FFFFFF',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity else return c,'#000000',0.2126 * r + 0.7152 * g + 0.0722 * b / opacity end end local function morethan(a,b) a = tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]') or '0') b = tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]') or '0') return a>b end local function lessthan(a,b) if tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) then a = tonumber(string.match(a, '%f[%d]%d[,.%d]*%f[%D]')) b = tonumber(string.match(b, '%f[%d]%d[,.%d]*%f[%D]') or '0') end return a<b end local function getsize(size) --size1 is between 1 and 3 values, each with px, equating to width,height,corner-rounding --eg '15px 25px 5px' (spaces are optional) or '18px'. returns three numbers local sizeval = {} for v in string.gmatch(size, "[^px]+") do table.insert(sizeval,v) end sizeval[1] = tonumber(sizeval[1]) or 13 sizeval[2] = tonumber(sizeval[2]) or sizeval[1] sizeval[3] = tonumber(sizeval[3]) or 0 return sizeval[1],sizeval[2],sizeval[3] end local function coord2text(coord) -- looks through the output from {{coord}} to find the lat and long decimal values -- and converts compass points to minus or not-minus, return with separating comma. local lat = string.match(coord,'[%.%d]+°[NS]') local lon = string.match(coord,'[%.%d]+°[EW]') local neg={N="",S="-",W="-",E=""} return neg[string.match(lat, '[NS]')]..string.match(lat,'[%.%d]+')..","..neg[string.match(lon, '[EW]')]..string.match(lon,'[%.%d]+') end local function convertCoordsTrad (row) local coords='' if row and string.find (row,'<span class="geo">') then local a,b=string.find(row,'<span class="geo">') local start=b+1 a,b=string.find(row,"</span>",b) local finish=a-1 coords=string.sub(row,start,finish) coords=string.gsub(coords,'; ',',') end return coords end local function convertCoords (row) local start,finish,lat,lon,coords,says if row then local a,b=string.find(row,"<span class=") start=a while a do -- find the final span> finish=b a,b=string.find(row,"span>",b) end if start then coords= string.sub(row,start,finish) says="" if string.find(coords,'<span class="error">') then error("coord error: badly formed coordinates",0) end coords=coord2text(coords) coords = string.sub(row,1,start-1)..coords..string.sub(row,finish+1) else coords=row end return coords else return "Nothing to see here" end end local function fillCommas(val,max) local line='' if not val then line=',' -- ensure there is some content else line = val --string.lower(string.gsub(val,"%s+","")) -- or strip spaces end if string.find(line,',') == 1 then line=' '..line end -- ensure initial comma is not skipped local _, count=string.gsub(line,",","") -- add enough subsequent commas for all entries line=line..string.rep(',',max-count) while(string.find(line,",,") ) do line=string.gsub(line,",,",", ,") --ensure string.gmatch doesn't ignore any empty items by padding with spaces end return line end local function makeLinkBox(left,top,wid,label, link) local linkBoxName='Transparent square.svg' if visibleLinks or '' =='yes' then linkBoxName='Red hollow square.svg' end local builder = mw.html.create('div') --display:inline-block; builder :cssText('position:absolute;left:'..tostring(left-1-wid/2)..'px;top:'..tostring(top-1 + math.min(wid/2-12,0) - wid/2)..'px') :wikitext(string.format( '[[File:%s|%dpx|link=%s|%s]]', linkBoxName, wid+2, link, label )) return tostring(builder) end local function extractItem(row,searchItem) -- remove text following a searchItem or start of line, which might be in quote-marks to allow commas local xend,xstart=1,0 if not row then return '','' end if searchItem then xend,xstart= string.find(row or '',searchItem or 'image:') end if not xstart then return string.gsub(string.gsub(row or '',"%b\"\"", ''),"%b\'\'", '') or '','' end while row:byte(xstart+1) == 32 and xstart<#row do -- skip over any leading spaces xstart=xstart+1 end local xbyte=row:byte(xstart+1) if xbyte == 34 or xbyte == 39 then -- are they wrapped in single or double quotes xstart=xstart+1 xend=row:find(string.char(xbyte),xstart+1) else xend = row:find(',',xstart+1) -- if no quotes, we assume no commas if not xend then xend=#row+1 end end -- return residual row and extracted text return row:sub(0,xstart)..row:sub(xend), row:sub(xstart+1,xend-1) end local function itemCheck(item,ext) if not item then return nil end if not ext then ext='' end return (string.match(item,"[%.%-?%d]+") or '0')..ext end local function stripdivs(line) return string.gsub(line or '',"%b<>", ' ') end local function splitItem(item,max) -- takes a commas-sep list and returns a table of lowercase items with no spaces, or nil local r={} local x=1 item=string.lower(fillCommas(item,max)) for t in string.gmatch(item,"[^,]+") do r[x]=string.gsub(t,"%s+","") if r[x]=='' then r[x]= nil else -- residual items might have commas if x>max then r[max]=(r[max] or '')..', '..r[x] end end x=x+1 end return r end local function ParseShapeTypes (result,args,sgval) -- for use with compressed, comma-separated 'sg plus dots' parameters --[[ shape table items and default values as set at top of page shapeType="0", Name="initialSettings", Parent="0", --sga items for the shape shape="circle", shapeSize="12px", shapeColor="blue", shapeAngle="0deg", --sgb items for border outlineWidth="0.5px", outlineColor="darkblue", outlineStyle="solid", --sgc items label text textSZ="11px", textCL="white", textNG="0deg", textAT=attributes ("bold" and/or "italic") --sgd items for dotTag tagSize="11px", tagColor="darkgrey", tagSpacer="0px", tagAngle="0deg", --sge extension line textEW="1px", textEC="darkblue", textES="solid" --sgf fx for text labels textSP="0px" textLH="100%" textOL="1px", textBG="paleground", <!--| sga = shape,Size,Color,Angle|sgb= outlineWidth,color,style eg: sga1=circle,14px,blue,0deg| sgb1=0px,dark grey,solid | sgc=textSize,color,angle,bold/italic | sgd=tagSize,Color,Spacer,Angle eg: sgc1=11px,dark grey,0deg,normal| sgd1=9px,white,0px,0deg | sge=lineWidth,color,style |sgf=textspacing,lineHeight%,outlinepx,backgroundcolor, [bold,italic] eg: sge1=0px,black, solid| sgf1=0px,120%,0px,background | dot=shape-group/lat/lon/title/dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> --]] if args["sgn"..sgval] then local sgname=string.match(args["sgn"..sgval],"(%w+)(.*)") sgNames[sgname]=sgval end local parent= args["sgp"..sgval] if parent then parent=string.match(parent,"(%w+)(.*)") local pos= string.find(parent,"%d+") if pos == 1 then parent=string.match(parent,"%d+") else parent=sgNames[parent] or '1' end end if sgval~='H' then if not parent or tonumber(parent) > tonumber(sgval) then if sgval=="1" then parent="0" else parent="1" end end end local itemTab, line, filename result[sgval]={} result[sgval].shapeType=sgval line,filename=extractItem(args['sga'..sgval] or '','image:') if sgval=='1' and not args.sga1 then line='circle,12px,blue,0deg' end -- ensure there is a parent=1 sga result[sgval].shapeFile=filename or '' -- sga= Attributes for shape itemTab=splitItem(line,4) result[sgval].shape = itemTab[1] or result[parent].shape result[sgval].shapeSize=itemTab[2] or result[parent].shapeSize result[sgval].shapeColor=itemTab[3] or result[parent].shapeColor result[sgval].shapeAngle=itemCheck(itemTab[4],'deg') or result[parent].shapeAngle -- sgb= Border outline attributes for shape itemTab=splitItem(args['sgb'..sgval],3) result[sgval].outlineWidth=itemCheck(itemTab[1],'px') or result[parent].outlineWidth result[sgval].outlineColor=itemTab[2] or result[parent].outlineColor result[sgval].outlineStyle=itemTab[3] or result[parent].outlineStyle --sgc=character attributes for label itemTab=splitItem(args['sgc'..sgval],4) result[sgval].textSZ=itemCheck(itemTab[1],'px') or result[parent].textSZ -- size of text in px result[sgval].textCL=itemTab[2] or result[parent].textCL -- colour for text result[sgval].textNG=itemCheck(itemTab[3],'deg') or result[parent].textNG -- Angle for text result[sgval].textAT=itemTab[4] or result[parent].textAT -- attributes bold, and/or italic --sgd=dotTag attributes itemTab=splitItem(args['sgd'..sgval],4) result[sgval].tagSize=itemCheck(itemTab[1],'px') or result[parent].tagSize result[sgval].tagColor=itemTab[2] or result[parent].tagColor result[sgval].tagSpacer=itemCheck(itemTab[3],'px') or result[parent].tagSpacer result[sgval].tagAngle=itemCheck(itemTab[4],'deg') or result[parent].tagAngle --sge= extension line attributes itemTab=splitItem(args['sge'..sgval],4) result[sgval].textEW=itemCheck(itemTab[1],'px') or result[parent].textEW -- width result[sgval].textEC=itemTab[2] or result[parent].textEC -- colour result[sgval].textES=itemTab[3] or result[parent].textES -- style --sgf= fx for label text itemTab=splitItem(args['sgf'..sgval],4) result[sgval].textSP=itemCheck(itemTab[1],'px') or result[parent].textSP -- spacing value for letters result[sgval].textLH=itemCheck(itemTab[2],'%') or result[parent].textLH -- Angle for text result[sgval].textOL=itemCheck(itemTab[3],'px') or result[parent].textOL -- width of text-border line result[sgval].textBG=itemTab[4] or result[parent].textBG -- color for text background return result end local function round(x,dec) -- x=number [, dec=integer] returns numeric value with upto dec decimals (all but first trailing zeros get truncated) if (dec or 0)==0 then return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5) --this avoids .0 where dec=0 end dec =10^(dec) return x>=0 and math.floor(x*dec+0.5)/dec or math.ceil(x*dec-0.5)/dec end local function maptogrid(t, r) r = r or 2 if type(t) ~= "table" then return 0, 0 end local lon = tonumber(t.lon) local lat = tonumber(t.lat) local lonbase = tonumber(t.lonbase) or lon local latbase = tonumber(t.latbase) or lat local zoom = tonumber(t.zoom) or 0 local width = tonumber(t.width) or 0 local height = tonumber(t.height) or 0 if not lon or not lat then return 0, 0 end local cosBase = math.cos(math.rad(latbase)) if cosBase == 0 or cosBase ~= cosBase then cosBase = 1 end local scale = 156543.03 * cosBase / (2 ^ zoom) if scale == 0 then scale = 1 end local x = width / 2 + ( ((math.rad(lon) * 6378137) - (math.rad(lonbase) * 6378137)) / scale ) * (1 - (0.075 * (math.abs(latbase) / 90))) local y = height / 2 + ( ((math.log(math.tan(math.rad(latbase) / 2 + math.pi / 4)) * 6378137) - (math.log(math.tan(math.rad(lat) / 2 + math.pi / 4)) * 6378137)) / scale ) * (1 - (0.075 * (math.abs(latbase) / 90))) return round(x, r), round(y, r) end local function getScale(zoom, lat, magVal) zoom = tonumber(zoom) or 0 lat = tonumber(lat) if not lat then lat = 0 -- fallback end if magVal then magVal = tonumber(magVal) if magVal > 1 and magVal <= 2 then zoom = zoom + (magVal - 1) end end local dist = (156543.03 * math.cos(math.rad(math.abs(lat))) / (2 ^ zoom)) / 17 local y if dist < 1 then y = round(dist * 10, 1) return tostring(y * 100) .. 'm', tostring(round(y * 109, 0)) .. 'yds' elseif dist < 18 then y = round(dist, 0) return tostring(y) .. 'km', tostring(round(y * 0.621371, 1)) .. 'miles' elseif dist < 500 then y = round(dist / 10) return tostring(y * 10) .. 'km', tostring(round(y * 6.21371, 0)) .. 'miles' else y = round(dist / 100) return tostring(y * 100) .. 'km', tostring(round(y * 62.1371, 0)) .. 'miles' end end local function ParseData (args,dotval) -- for use with compressed, comma-separated 'sg plus dots' parameters -- takes a structured series of comma-separated items which get parsed as the following: -- dot(n)= (sgNumber or Name),{{coord}} or (lat,lon), (dotTag) -- dotlink(n) = single-parameter text to give wikilink and/or title used by tootlip, fullscreen dots and autocaption list -- dotlabel(n) = 'label text',pos(left,roght,top,bottom,centre,auto),(dx), (dy) pixel offsets, params, info -- dotpic(n) = single parameter wikimedia filename for an image to use in photopanel and/or fullscreen dots -- dotfeature(n)= 'mark-line' (,linewidth,style,gap) or 'photo-panel' (,image-dim,panel-width,panel-height), draws line to n-1 -- label is used if either a position and/or an x,y offset are not 0,0 ( if no label then dotTag will be put at at the x,y offset, or over the dot -- label text can be autoaligned if x,y puts it left or right of the dot, or centered if above/below) -- quote marks are not needed unless including commas within the label text -- param1 is optional items separated by spaces, and can include [nolabel nolist nomap hemisphere+1 hemisphere-1] -- info is free wikitext, to be used in the fullscreen dot box. (use dotpic to show a picture) --<!--| dotx=shape-group,[lat,lon or {{coord}} ], dotTag | dotlink=link or tooltip | dotlabel=label,position,dx,dy,param1,info| dotpic=filename--> local result={} local count=1 local row = convertCoords (args["dot"..dotval]) -- swap in any {{coord}} values so they are csv lat and lon row=fillCommas(row,4) result.code=dotval -- store the parameter name as the id code for item in string.gmatch(row,"[^,]+") do -- iterate through 'row', adding each csv item in turn, if present if count==1 then --see if it is a number or a name local pos= string.find(item,"%d+") if pos == 1 then result.group=string.lower(string.gsub(item,"%s+","")) else item=string.match(item,"(%w+)(.*)") -- ensure just a single word result.group=sgNames[item] end elseif count==2 then result.lat=tonumber(string.match(item,"[%.%-?%d]+")) or 0-- find the number, with no non-numeric stuff elseif count==3 then result.lon=tonumber(string.match(item,"[%.%-?%d]+")) or 0 elseif count==4 then result.dotTag=item:match( "^%s*(.-)%s*$" ) or "" -- dotTag allows for internal spaces, but no commas end count=count+1 end row, result.labelText= extractItem(args["dotlabel"..dotval]) result.labelText= string.gsub(result.labelText,"[%^]+","<br>") -- convert hats to line breaks local item=splitItem(row,6) result.labelPos=item[2] or 'center' result.dx=tonumber(string.match(item[3] or '0',"[%.%-?%d]+")) or 0 result.dy=tonumber(string.match(item[4] or '0',"[%.%-?%d]+")) or 0 result.param1=string.lower(item[5] or '') if string.find(result.param1,"hemisphere-1",1,true) then result.lon=result.lon-360 elseif string.find(result.param1,"hemisphere+1",1,true) then result.lon=result.lon+360 end local txt = '' if item[6] then -- ensure all info elements are included, including commas count=1 local max=6 for t1 in string.gmatch(fillCommas(row,max),"[^,]+") do if count>max then txt=txt..',' end if count>=max then txt=txt..t1 end count=count+1 end end result.info=txt result.imageName = args['dotpic'..dotval] -- Get first wikilinked item (if any) from the args.dotlink and set this plus the delinked text local testx=args["dotlink"..dotval] or '' result.dotLink=testx if testx ~= '' then testx=stripdivs(testx) result.title=delink({ testx }) local linkstart= string.find(testx,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then result.dlink=delink( { string.sub(testx,linkstart,string.find(testx,']]',1,true)+1),wikilinks='target' } ) else result.dlink='' end else result.dlink='' result.title='' end if args['dotfeature'..dotval] then local item=splitItem(args['dotfeature'..dotval],6) if (item[1] or '') =='photo-panel' then result.ppwidth= tonumber(string.match((item[3] or '110'),"%d+")) -- panel width result.ppheight= tonumber(string.match((item[4] or '48'),"%d+") ) --panel height result.photowidth=round(tonumber(string.match((item[2] or '1.3'),"[%.%-?%d]+")) * result.ppheight+1,0) -- dimension to set image size result.photoImage=result.imageName result.posType='photo-panel' elseif (item[1] or '') == 'mark-line' then local x=tonumber(dotval or '0') result.markDest=item[5] or tostring(x-1) result.mlWidth= tonumber(string.match((item[2]) or '',"%d+") or '1') result.mlStyle= item[3] or 'solid' result.mlGap=tonumber(string.match((item[4] or ''),"[%d]+") or '0') result.posType='mark-line' -- debugmsg('making line for '..tostring(x)..'to '..tostring(result.markDest)..' with width '..tostring(result.mlWidth)) end --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end maplist.lon=result.lon maplist.lat=result.lat result.gridx, result.gridy = maptogrid(maplist,1) -- convert geo coords to grid xy - using values from maplist table return result end local function multiCheck (args, argName, argVal, defVal, alt) if not alt then alt='nonexistant' end if argVal=='H' and not args[argName..'H'] then argVal=highlightNum or '1' end if argVal=='' then return (args[argName] or args[alt] or (args[argName..'D']) or args[alt..'D'] or defVal) -- unnumbered args do not inherit from D or 1 else return (args[argName..argVal]) or (args[alt..argVal]) or (args[argName..'D']) or (args[alt..'D']) or (args[argName..'1']) or (args[alt..'1']) or defVal end end local function assignTradstyleShape(shapeResult,default,dotResult,args,nval) local item,itemTab local autoDotTag='' local shapeWidth,shapeHeight=0,0 local argval=nval -- to catch the unnumbered shape series if argval=='0' then argval='' end if nval=='H' then shapeResult.H={} end item=string.lower(multiCheck(args,'shape',argval,'image')) if string.find(item,'n-',0,true)==1 or string.find(item,'l-',0,true)==1 then autoDotTag=string.sub(item,0,1) item=string.sub(item,3) end if item == 'image' then shapeResult[nval].shape = 'image:' shapeResult[nval].shapeFile =multiCheck(args,'mark',argval,'Red pog.svg') shapeWidth=-1 else shapeResult[nval].shape = item end item= multiCheck(args,'mark-size',argval,'14px') local a,b,c= getsize(string.gsub(string.gsub(item,',','px')..'px','pxpx','px')) if b==a and args['mark-dim'..argval] then b= b / tonumber(string.match(args['mark-dim'..argval],"[%.%-?%d]+")) end shapeHeight=b/2 item=tostring(a)..'px'..tostring(b)..'px'..tostring(c)..'px' shapeResult[nval].shapeSize= item itemTab=splitItem(multiCheck(args,'shape-color',argval,'hard red'),2) shapeResult[nval].shapeColor=itemTab[1] or 'hardred' item=itemCheck(itemTab[2],'%') -- jump through the various opacity hoops and add to color if needed if not item then item=itemCheck(args['shape-opacity'..argval],'%') end if item and item~='0%' and item~='100%' then shapeResult[nval].shapeColor=shapeResult[nval].shapeColor..item end shapeResult[nval].shapeAngle=itemCheck(multiCheck(args,'shape-angle',argval,'0'),'deg') or '0deg' --sort out the outline entry itemTab=splitItem(multiCheck(args,'shape-outline',argval,'transparent,0,100,solid'),4) shapeResult[nval].outlineColor=itemTab[1] or 'dark grey' shapeResult[nval].outlineWidth=itemCheck(itemTab[2],'px') or '1px' if itemTab[3] and itemTab[3]~='100' and itemTab[3]~='0' then shapeResult[nval].outlineColor=shapeResult[nval].outlineColor..itemCheck(itemTab[3],'%') end shapeResult[nval].outlineStyle=itemTab[4] or 'solid' -- label size, background, outline itemTab=splitItem( multiCheck(args,'label-size',argval,'12'),3) shapeResult[nval].textSZ=itemCheck(itemTab[1],'px') or '12px' if itemTab[2]=='outline' then shapeResult[nval].textBG=itemTab[3] or 'transparent' shapeResult[nval].textOL='1px' elseif itemTab[3]=='outline' then shapeResult[nval].textBG=itemTab[2] or 'transparent' shapeResult[nval].textOL='1px' else shapeResult[nval].textOL='0px' shapeResult[nval].textBG=itemTab[2] or 'transparent' end if getColor(shapeResult[nval].textBG)==CTB['hardgrey'] and shapeResult[nval].textBG~='hardgrey' then shapeResult[nval].textBG= 'transparent' end --label color etc itemTab=splitItem(multiCheck(args,'label-color',argval, 'darkgrey','label-colour'),2) shapeResult[nval].textCL=itemTab[1] or 'darkgrey' if itemTab[2] and itemTab[2]~='0%' and itemTab[2]~='100%' then shapeResult[nval].textCL=shapeResult[nval].textCL..itemTab[2] end shapeResult[nval].textSP=itemCheck( multiCheck(args,'label-spacing',argval,'0'),'px') -- sets letter-spacing in px shapeResult[nval].textLH=itemCheck( multiCheck(args,'label-height',argval,'120'),'%') -- sets line height, 120% default shapeResult[nval].textNG=itemCheck(multiCheck(args,'label-angle',argval,'0'),'deg') --sgd=dotTag attributes shapeResult[nval].tagSize=tostring(shapeHeight*1.5)..'px' local c1,c2=checkColors(shapeResult[nval].shapeColor) shapeResult[nval].tagColor=c2 shapeResult[nval].tagSpacer='0px' shapeResult[nval].tagAngle='0deg' -- sge extension line attributes local shapePos=splitItem(multiCheck(args,'label-pos',argval,'right'),6) if shapePos[2]=='with-line' or shapePos[2]=='n-line' then shapeResult[nval].textEW=(shapePos[3] or '1')..'px' -- width shapeResult[nval].textEC=(shapePos[4] or shapeResult[nval].shapeColor or 'darkgrey') elseif shapePos[2]=='photo-panel' then shapeResult[nval].textEW='2px' -- width shapeResult[nval].textEC=shapeResult[nval].textCL else shapeResult[nval].textEW='0px' -- width shapeResult[nval].textEC='grey'-- colour end shapeResult[nval].textES='solid' if argval=='H' then return dotResult end --Assign dot values local dotItem={} dotItem.group=nval dotItem.code=nval dotItem.posType=shapePos[2] or 'nil' if (shapePos[2] or '') =='photo-panel' then dotItem.ppwidth= tonumber(string.match((shapePos[4] or '110'),"%d+")) dotItem.ppheight= tonumber(string.match((shapePos[5] or '48'),"%d+") ) dotItem.photowidth=round(tonumber(string.match((shapePos[3] or '1.3'),"[%.%-?%d]+")) * dotItem.ppheight+1,0) dotItem.photoImage=args['mark-image'..argval] --debugmsg('photo-panel, '..shapePos[2]..', 3='..shapePos[3]..', 4='..shapePos[4]..', 5='..(shapePos[5] or '(48')..'photowidth='..tostring(dotItem.photowidth)) end if (shapePos[2] or '') =='mark-line' then local x=tonumber(argval or '1') dotItem.markDest=shapePos[6] or tostring(x-1) dotItem.mlWidth= tonumber(string.match((shapePos[3] or '1'),"%d+")) dotItem.mlStyle= shapePos[4] or 'solid' dotItem.mlGap=tonumber(string.match((shapePos[5] or '0'),"[%d]+")) shapeResult[nval].textEC=shapeResult[nval].outlineColor or 'darkgrey' end if args['mark-coord'..argval] then local raw = args['mark-coord'..argval] local lat, lon lat, lon = string.match(raw, "{{coord|([%-%.%d]+)|([%-%.%d]+)}}") if not lat or not lon then lat, lon = string.match(raw, "([%-%.%d]+)%s*[,;]%s*([%-%.%d]+)") end if not lat or not lon then local cleaned = convertCoordsTrad(raw) if cleaned then local tab = splitItem(cleaned, 2) lat = tab[1] and string.match(tab[1], "[%-%.%d]+") lon = tab[2] and string.match(tab[2], "[%-%.%d]+") end end dotItem.lat = tonumber(lat) or 0 dotItem.lon = tonumber(lon) or 0 else dotItem.lat = tonumber(string.match(args['mark-lat'..argval] or "", "[%-%.%d]+")) or 0 dotItem.lon = tonumber(string.match(args['mark-lon'..argval] or "", "[%-%.%d]+")) or 0 end if args['dateline'..argval] and (args['dateline'..argval]=='1' or args['dateline'..argval]=='-1') then dotItem.lon=dotItem.lon+(tonumber(args['dateline'..argval] ) *360) end maplist.lon=dotItem.lon maplist.lat=dotItem.lat dotItem.gridx, dotItem.gridy = maptogrid(maplist,1) local item=args['mark-title'..argval] or '' -- sort out the caption, wikilink and plaintext tooltip items from dotLink if item=='none' then dotItem.param1='nomap nolist' item='' end dotItem.dotLink=item if item ~= '' then item=stripdivs(item) dotItem.title=delink({item}) local linkstart= string.find(item,'[[',1,true) -- use true to ensure a plain search (no pattern) if linkstart then dotItem.dlink=delink({string.sub(item,linkstart,string.find(item,']]',1,true)+1),wikilinks='target'}) else dotItem.dlink='' end else dotItem.dlink='' dotItem.title='' end if autoDotTag=='n' then item=nval elseif autoDotTag=='l' then item=string.char(64+tonumber(nval)) else item='' end dotItem.dotTag = args['numbered'..argval] or item if shapePos[2]=='n-line' and (args['label'..argval] or args['label'..argval]=='') then if dotItem.dlink =='' then item=dotItem.dotTag..' '..args['label'..argval] else item='[['..dotItem.dlink..'|'..dotItem.dotTag..']] '..args['label'..argval] end else item=(args['label'..argval] or '') end if args['labela'..argval] then item = item..'^'..args['labela'..argval] end if args['labelb'..argval] then item = item..'^'..args['labelb'..argval] end local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end dotItem.labelText = a -- convert hats to line breaks if argval=='' then item = (args['label-offset-x']) or (args.ldx) or '0' else item=args['label-offset-x'..argval] or args['ldx'..argval] or args['label-offset-xD'] or args.ldxD or args['label-offset-x1'] or args.ldx1 or '0' end dotItem.dx=tonumber(string.match(item,"[%.%-?%d]+")) if argval=='' then item = (args['label-offset-y']) or (args.ldy) or '0' else item=args['label-offset-y'..argval] or args['ldy'..argval] or args['label-offset-yD'] or args.ldyD or args['label-offset-y1'] or args.ldy1 or '0' end dotItem.dy=tonumber(string.match(item,"[%.%-?%d]+")) dotItem.labelPos=shapePos[1] if args['mark-image'..argval] then dotItem.imageName= args['mark-image'..argval] end dotItem.info=args['mark-description'..argval] or '' table.insert(dotResult,1,dotItem) -- add to start of list, so they are in reverese order for displaying return dotResult end local function tradstyleParseShapes(args,dotTable,dotmax) local sgNumbers,sgSortable={},{} for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) then sgNumbers[x]=x -- add it to the list end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form TODO this must be possible in a single list! if args['mark-coord'] or (args['mark-lat'] and args['mark-lon']) then table.insert(sgSortable,'0') end -- add it to the end of the list local default={} default.D={} local dotResult={} for k,sgnum in pairs(sgSortable) do -- work through the sorted list, parsing each set of shapes in turn, from 1 upwards shapeList[sgnum]={} dotTable=assignTradstyleShape(shapeList,default,dotTable,args,sgnum) end dotTable=assignTradstyleShape(shapeList,default,dotTable,args,'H') -- construct an extra highlight shapeitem return dotTable end local function checkfortooltip (title,dx,dy,dotlabel,dlink,nolabel) -- returns tlink if available, and dlink, if needed and tshape=true if shape needed local tshape,tlink = false,"" if dlink~='' and not nolabel then tlink=dlink end if (tlink=="" or nolabel) and title~="" then tshape=true end -- tshape flags True if title is wanted for shape if (not (dx==0 or dy==0) or dotlabel=='') and title~='' then tshape=true end -- add tooltip to shape if its number has moved return tshape,tlink end local function tshift(angle) -- adjustment to place text near the centre of a triangle, shifted to allow rotation of triangle shape local x=tonumber(string.match(angle,"%-?%d+")) if x<0 then x=360+x end -- set to a single degree direction, 0 to 360 if x>359 then return 0,0 end -- shift the centre of the triangle based on rotation value if x <=40 or x>=320 then return -0.17,0 -- triangle up= -shiftv elseif x>=140 and x<=220 then return 0.17,0 --triangle down= +shiftv elseif x >220 then return 0,-0.17 --triangle left= -shifth elseif x >40 then return 0,0.22 --triangle right= +shifth end return 0,0 end local function makeTriangle(result,row,shape,outline,tlink) local w,h,r=getsize(shape.shapeSize) if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) w=w+p*2 h=h+p*2 end table.insert(result,'<div ') if tlink then table.insert(result,' title="'..row.title..'" ') end table.insert(result,'style="display:inline-block; position: absolute') if shape.shapeAngle ~= '0deg' then table.insert(result,'; transform: rotate('..shape.shapeAngle..')') end local shiftv,shifth=0,0 shiftv,shifth=tshift(shape.shapeAngle) table.insert(result,'; top: '..tostring(row.gridy-h/2+h*shiftv)..'px') table.insert(result,'; left: '..tostring(row.gridx-w/2+w*shifth)..'px; width: 0; height: 0; outline-width: 0px') table.insert(result,'; border-left: '..tostring(w/2)..'px solid transparent') table.insert(result,'; border-right: '..tostring(w/2)..'px solid transparent') if outline then -- fill with outline colour, to make a 'base layer' or shape colour table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.outlineColor).. '">') else table.insert(result,'; border-bottom: '..tostring(h)..'px solid '..getColor(shape.shapeColor).. '">') end table.insert(result,'</div>') end local function makeSquare(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) if shape.shape=='square' then h=w end -- squares are always square!. box can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end if r~=0 then div:css('border-radius',tostring(r).."px") end if shape.shape=='panel' then div:css('top', tostring(row.gridy).."px") div:css('left', tostring(row.gridx).."px") else div:css('top', tostring(row.gridy-h/2).."px") div:css('left', tostring(row.gridx-w/2).."px") end div:css('width', tostring(w).."px") div:css('height', tostring(h).."px") div:css('background-color', getColor(shape.shapeColor) ) div:css('color', 'inherit') table.insert(result,tostring(div)) end local function makeCircle(result,row,shape,tshape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding if shape.shape=='circle' then h=w end -- circles are always round. ellipse can stretch local div=mw.html.create ('div') if tshape then -- Add tooltip if needed div:attr('title',row.title) end div:css('position', "absolute") if shape.outlineWidth ~= "0px" then div:css('outline', shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) end if shape.shapeAngle ~= "0deg" then div:css('transform',"rotate("..shape.shapeAngle..")") end div :css('top', tostring(row.gridy-h/2).."px") :css('left', tostring(row.gridx-w/2).."px") :css('width', tostring(w).."px") :css('height', tostring(h).."px") :css('border-radius', "50%") :css('background-color', getColor(shape.shapeColor) ) :css('color', 'inherit') table.insert(result,tostring(div)) end local function makeRuleA(result,row,shape) local w,h,r=getsize(shape.shapeSize) -- = width,height,rounding local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local lineV=0 if shape.shape=='rulea' then lineV=oWid*3+16 end table.insert(result,"<div style=\"display:inline-block; position: absolute") -- create a square transparent container, which will rotate line and arrow together table.insert(result,"; top:"..tostring(row.gridy - w/2).."px") table.insert(result,"; left:"..tostring(row.gridx - w/2).."px") table.insert(result,"; width:"..tostring(w).."px") table.insert(result,"; height:"..tostring(w).."px; background:transparent; color:inherit;") table.insert(result,"; transform: rotate( "..tostring(tonumber(string.match(shape.shapeAngle,"[%.%-?%d]+")) - 90).."deg);\">" ) table.insert(result,"<div style=\"display:inline-block; position: absolute") -- put the line (as a border-right) across the container table.insert(result,"; top:"..tostring(lineV).."px") table.insert(result,"; left:"..tostring((w - oWid )/2).."px; width: 0px") table.insert(result,"; height: "..tostring(w -lineV).."px") table.insert(result,"; border-right: "..shape.outlineWidth.." "..shape.outlineStyle.." "..getColor(shape.outlineColor)) table.insert(result,"; background:transparent; color:inherit;\"></div>") if shape.shape=='rulea' then table.insert(result,"<div style=\"display:inline-block; position: absolute; top: 0px") --and add arrow head table.insert(result,"; left:"..tostring(w/2-( oWid/2)-oWid*0.55-2).."px; width: 0; height: 0; outline-width: 0px") table.insert(result,"; border-left: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-right: "..tostring(oWid*1.1+2).."px solid transparent") table.insert(result,"; border-bottom: "..tostring(oWid*3+16).."px solid "..getColor(shape.outlineColor).."\"></div>") end table.insert(result,"</div>") end local function makeCurveA(result,row,shape) -- draw a curve with Arrow ----- local w,h=getsize(shape.shapeSize) -- = width,height local oWid=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) local Angle=tonumber(string.match(shape.shapeAngle,"[%.%d]+")) table.insert(result,'<div style="position: absolute;') --set up out div, which will allow the whole to rotate table.insert(result,'top:'..tostring(row.gridy - (w + oWid*3+16)/2)..'px;') table.insert(result,'left:'..tostring(row.gridx - ( w + oWid*3+16)/2)..'px; ') table.insert(result,'width: '..tostring(w+oWid*3+16)..'px; ') table.insert(result,'height: '..tostring(w+oWid*3+16)..'px; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate( '..tostring(Angle-120)..'deg);">') else table.insert(result,'transform: rotate( '..tostring(Angle -62)..'deg);">') end table.insert(result,'<div style="position: absolute;') --set up div for the rounded corner of a rectangle table.insert(result,'border-left: '..shape.outlineWidth..' '..shape.outlineStyle..' '..getColor(shape.outlineColor)..';') if shape.shape=='curvea' then table.insert(result,'border-radius: 10000px 0 0 '..tostring(w)..'px; top:0px; left:'..tostring(w*0.25)..'px;') else table.insert(result,'border-radius: '..tostring(w)..'px 0 0 10000px;') table.insert(result,'top:'..tostring((oWid*3+16)/2+w*0.15)..'px; left:'..tostring(w*0.25)..'px;') end -- and add a triangular arrow head table.insert(result,'width: '..tostring(w)..'px; height: '..tostring(w)..'px;"></div><div style="position: absolute; ') if shape.shape=='curvea' then table.insert(result,'transform: rotate(180deg); top:'..tostring(w-1)..'px; ') else table.insert(result,'transform: rotate(0deg); top: '..tostring(0-( ( oWid*3+16)/2)+1+( w*0.15) )..'px;') end -- reverse table.insert(result,'left:'..tostring(0-( oWid*0.6)-2+(w*0.25))..'px;') table.insert(result,'width: 0; height: 0; outline-width: 0px; border-left: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-right: '..tostring(oWid*1.1+2)..'px solid transparent;') table.insert(result,'border-bottom: '..tostring(oWid*3+16)..'px solid '..getColor(shape.outlineColor)..';"></div></div>') end local function makeLineTo (result,x1,y1,x2,y2,oWid, oStyle, oCol,double) table.insert(result,"<div style=\"display:inline-block; position: absolute;") -- draw a line between x1,y1 and x2,y2, px-coords where 0,0 is centre of frame -- Maths calculations thanks to ES table.insert(result,"left: "..tostring(x1+( (x2-x1)/2) - (math.sqrt( ( x2-x1)^2 + (y2-y1)^2 )/2)-1).."px;") table.insert(result,"top: "..tostring(y1+( ( y2-y1 )/2) ).."px;") table.insert(result,"width: "..tostring(math.sqrt( (x2-x1 )^2 + ( y2-y1 )^2) ).."px;") table.insert(result,"height: "..tostring(double).."px; background-color:transparent; color:inherit; ") table.insert(result,"outline-width: 0; border-bottom: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) if double>1 then table.insert(result,"border-top: "..oWid.." "..oStyle.." "..getColor(oCol)..";" ) end if x1==x2 then table.insert(result,"transform: rotate(90deg);") else table.insert(result,"transform: rotate("..tostring(math.atan(( y2-y1)/( x2-x1 ) )*180/math.pi).."deg);\"></div>") end end local function makeClipPath(result,row,shape,outline,tshape) --tshape is a flag to show if the tooltip (title=) is wanted -- return the text css div code to position and draw a shape occupying a specified clippath if not pathshape[shape.shape] then debugmsg(mw.addWarning('shape'..row.code..' = '..shape.shape..'. The shape name is not defined')) return end local w,h,r=getsize(shape.shapeSize) if string.match(shape.shape,"circle") or string.match(shape.shape,"square") then h=w end -- use ellipse and box for stretched shapes local shifth,shiftv = 0,0 if string.match(shape.shape,"triangle") then shiftv,shifth =tshift(shape.shapeAngle) end if outline then local p=tonumber(string.match(shape.outlineWidth,"[%.%d]+")) or 0 w=w+p*2 h=h+p*2 end table.insert(result,"<div ") if tshape then -- Add tooltip if needed table.insert(result," title=\""..row.title.."\" ") end table.insert(result,"style=\"display:inline-block; position: absolute; background-color:") if outline then table.insert(result,getColor(shape.outlineColor)) -- fill with outline colour, to make a 'base layer' else table.insert(result,getColor(shape.shapeColor)) end table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;"..pathshape[shape.shape].."&#39;) ") -- adds the required clippath data from the table of pathshape string literals table.insert(result,"; top:"..tostring(row.gridy - 10 + h*shiftv).."px") table.insert(result,"; left:"..tostring(row.gridx - 10 + w*shifth).."px") table.insert(result,"; width:20px") -- needs to be a path within a 20px20px box, and then rescales using size values to match other shape sizes table.insert(result,"; height:20px; transform:scale("..tostring(w/16)..", "..tostring(h/16)..")") if shape.shapeAngle ~= "0deg" then table.insert(result," rotate("..shape.shapeAngle..")") end table.insert(result,"\"></div>") end local function makeImage(result,row,shape) local w,h,r=getsize(shape.shapeSize) local image=shape.shapeFile if not image or image=='' then image='Red pog.svg' end local imagediv=mw.html.create ('div') imagediv:css('position', "absolute") if shape.shapeAngle ~= "0deg" then imagediv:css('transform',"rotate("..shape.shapeAngle..")") end imagediv :css('top', (row.gridy-1 + math.min(h/2-12,0) - h/2).."px") --File seems to adjust pos for small images :css('left', (row.gridx-1-w/2).."px") :css('background-color', "transparent" ) :css('color','inherit') :wikitext('[[file:'..image..'|'..tostring(w+2)..'px|alt='..(row.title or '')..'|link=]]') table.insert(result,tostring(imagediv)) end local function makePhotoPanel(result,row,shape) local h=row.ppheight table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;' ) table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') table.insert(result, 'width: '..tostring(row.ppwidth)..'px; height: '..tostring(h)..'px; border-radius: 2px; color:inherit;') table.insert(result, 'background-color: #E8E8D6; outline: 2px solid '..getColor(shape.textCL)..'; box-shadow: 2px 2px 4px #33203335;"></div>') if row.photoImage and row.photowidth >0 then table.insert(result, '<div style="position: absolute; top: '..tostring(row.gridy+row.dy-h/2)..'px;') if row.labelPos=='left' or string.find(row.labelPos,'west') then --debugmsg(row.labelText..', Align=right, ppwidth='..tostring(row.ppwidth)..', photowidth='..row.photowidth..', dx='..tostring(row.dx)..', dy='..tostring(row.dy)) table.insert(result, 'left: '..tostring(row.gridx+row.dx - row.ppwidth/2)..'px;') row.dx=row.dx+row.photowidth/2 else --debugmsg(row.labelText..', Align=left, ppwidth='..tostring(row.ppwidth)..', photowidth='..row.photowidth..', dx='..tostring(row.dx)..', dy='..tostring(row.dy)) table.insert(result, 'left: '..tostring(row.gridx+row.dx + (row.ppwidth-row.photowidth) - row.ppwidth/2)..'px;') row.dx=row.dx-row.photowidth/2 end table.insert(result, 'background-color:transparent; color:inherit; border-radius: 2px;">') table.insert(result,'[[File:'..row.photoImage..'|x'..tostring(h)..'px|File:'..row.photoImage..']]</div>') row.labelPos='center' end end local function makePanelText(result, row, shape) local w,h,r=getsize(shape.shapeSize) local ty=tonumber(string.match(shape.textSZ or '11',"%d+") ) table.insert(result,'<div style="position:absolute; line-height: 120%; font-size: '..shape.textSZ..'; color:'..getColor(shape.textCL)..';') if row.labelPos == 'left' or string.find(row.labelPos,'west') then table.insert(result,'top: '..tostring(row.dy+row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+3)..'px; text-align: left; width:'..tostring(w)..'px;') elseif row.labelPos == 'right' or string.find(row.labelPos,'east') then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+w-3)..'px; text-align: right; width: max-content; transform: translateX(-100%);') elseif row.labelPos == 'top' or row.labelPos== 'north' then table.insert(result,'top: '..tostring(row.gridy+(ty/3))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') elseif row.labelPos == 'bottom' or row.labelPos=='south' then local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*1.1 table.insert(result,'top: '..tostring(row.gridy+w-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') else -- center or centre local bry=(select(2, string.gsub(row.labelText,"<br>", ""))+1)*0.6 table.insert(result,'top: '..tostring(row.gridy+(h/2)-(ty*bry))..'px;') table.insert(result,'left: '..tostring(row.gridx+(w/2))..'px; text-align: center; width:max-content; transform: translateX(-50%);') end if shape.textSP and shape.textSP ~='0px' then table.insert(result,"letter-spacing: "..shape.textSP..';') end if shape.textLH and shape.textLH ~='120%' then table.insert(result,"line-height: "..shape.textLH..';') end table.insert(result,"vertical-align: middle;\">"..row.labelText.."</div>") end local function makeTextItem(result, row, shape, align, tlink, textItem, dotItem) local w,h,r=getsize(shape.shapeSize) table.insert(result,"<div ") if row.title ~= "" then table.insert(result," title=\""..row.title.."\" ") end local ty,bry,linkoffset = 0,0,0 local compy=0 local lh=tonumber(string.match(shape.textLH or '120',"%d+"))/100 if dotItem==1 or (dotItem==2 and row.posType~='n-line') then -- if there is a dotTag in the middle of the shape then use the tag settings ty=tonumber(string.match(shape.tagSize,"%d+")) or 0 table.insert(result,"style=\"position:absolute; line-height: 120%; top: "..tostring(row.gridy-ty*0.62)) table.insert(result,"px; left: "..tostring(row.gridx).."px; width: fit-content; ") table.insert(result,"text-align: center; color: "..getColor(shape.tagColor).."; background-color: transparent;") local trf="" if shape.tagAngle ~="0deg" then trf=" rotate("..shape.tagAngle..")" end table.insert(result, "transform: translateX(-50%)"..trf.."; font-size: "..shape.tagSize..";") if shape.tagSpacer~='0px' then table.insert(result, "letter-spacing:"..shape.tagSpacer..";") end else -- or add tfx settings for left, right or center align, colors, backgrounds, border-outline table.insert(result,'style="position:absolute; ') if dotItem==2 then -- dotTag is out at x,y so 85% ty=tonumber(string.match(shape.tagSize or '0',"%d+")) table.insert(result,'font-size: '..shape.tagSize..'; padding:0px 2px;line-height: 85%; top: '..tostring(row.dy+row.gridy-ty*0.52)) else -- it is labelText, so use textLH or 120% ty=tonumber(string.match(shape.textSZ or '11',"%d+")) if row.labelPos=='northwest' or row.labelPos=='northeast' then compy=-ty elseif row.labelPos=='southeast' or row.labelPos=='southwest' then compy=ty/2 end if row.labelPos and not (row.labelPos== 'auto' or row.labelPos=='') then bry=(select(2, string.gsub(row.labelText,"<br>", ""))*lh) -- is it a multiline text? expand by line-height /120%? if row.posType == 'with-line' then bry=0 end if row.labelPos=='bottom' or row.labelPos == 'south' then bry = 0 -- and shift by none, all or half elseif row.labelPos=='top' or row.labelPos == 'north' then if shape.shape=='image:' then bry= 1 + math.min(w/2-10,0)-bry*ty else bry= -bry*ty+2 end else bry=-bry*(ty/2 * lh) end end if row.posType == 'photo-panel' then bry=bry+3 end table.insert(result,'font-size: '..shape.textSZ..'; padding:0px 3px;line-height: '..shape.textLH..'; ') table.insert(result,'top: '..tostring(row.dy+row.gridy+compy+bry-ty*lh/2)) end table.insert(result,"px; left: "..tostring(row.dx+row.gridx).."px; color: "..getColor(shape.textCL).."; ") table.insert(result,"width: max-content; ") local trf="" if shape.textNG ~="0deg" then trf="rotate("..shape.textNG..")" end if shape.textOL~="0px" then table.insert(result,"background-color: "..getColor(shape.textBG).."; ") table.insert(result,"border: "..shape.textOL.." solid "..getColor(shape.textCL).."; border-radius:6px;") else table.insert(result,"background-color: transparent;") end if row.labelPos=="right" or string.find(row.labelPos,'east') then table.insert(result,"text-align: left; ") linkoffset=w if shape.textNG ~="0px" then table.insert(result,"transform-origin: left; transform: rotate("..shape.textNG.."); ") end elseif row.labelPos=="left" or string.find(row.labelPos,'west') then if shape.textNG ~="0px" then table.insert(result,"transform-origin: right;") end linkoffset=-w table.insert(result,"text-align: right; transform: translateX(-100%) "..trf.."; ") else table.insert(result,"text-align: center; transform: translateX(-50%) "..trf.."; ") end table.insert(result,'font-weight: normal; line-height: '..shape.textLH..'; letter-spacing:'..shape.textSP..'; vertical-align: bottom;') end if string.find(shape.textAT or '','bold') then textItem='<b>'..textItem..'</b>' end if string.find(shape.textAT or '','italic') then textItem='<i>'..textItem..'</i>' end if shape.textOL=='0px' and shape.textBG~='transparent' and dotItem==0 then table.insert(result,'\"><span style=\"background-color: '..getColor(shape.textBG)..'; color:inherit;\">'..textItem..'</span></div>') else table.insert(result,"\">"..textItem.."</div>") end if tlink~='' and not string.match(row.param1 or "","nolink") then table.insert(result,makeLinkBox(row.gridx+row.dx+linkoffset, row.gridy+row.dy+bry, 16, row.dotTag..' '..row.title,tlink)) end end local function getshapetable(row,shape) -- Construct CSS divs for a dot from shape and map data local result={} local w,h,r=getsize(shape.shapeSize) local tshape,tlink=checkfortooltip(row.title,row.dx,row.dy,row.dotTag,row.dlink,string.match(row.param1 or "","nolink") ) local align=row.labelPos or '' local offsetx,offsety=0,0 local ty=tonumber(string.match((shape.tagSize or 9),"%d+" )) if row.labelText and row.labelText~='' then ty=tonumber(string.match((shape.textSZ or 11),"%d+" )) else align='center' end -- it is just for dotTag, so justify center -- identify align value and extend offsets local widthzone,heightzone = w/2+4,h/2+4 local theta,r = math.deg( math.atan2(row.dy, row.dx)) , math.sqrt(row.dx^2 + row.dy^2) if align=='auto' or align=='' then if (theta < -112 or theta > 112) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "left" offsetx=1 elseif (theta > -68 and theta < 68) and math.abs(row.dx)>=w/2 and math.abs(row.dy)<w/1.4 then align = "right" offsetx=-1 elseif theta <0 then offsety=ty/2-1 -- bottom else offsety=0-ty/2+1 -- top end elseif align=='left' or string.find(align,'west') then row.dx=row.dx - w/2 widthzone=4 offsetx=-1 elseif align=='right' or string.find(align,'east') then row.dx=row.dx + w/2 widthzone=4 offsetx=1 offsety=-ty/2 +1 elseif align=='top' or align=='north' then if string.find(shape.shape,'curve') then row.dy=row.dy-(h/2)-12 heightzone=4 offsety=ty/2-1 else row.dy=row.dy - h-2 heightzone=4 offsety=ty/2-1 end elseif align=='bottom' or align=='south' then if string.find(shape.shape,'curve') then row.dy=row.dy+(h/2) heightzone=4 offsety=ty/2-1 else row.dy=row.dy + h+2 heightzone=4 offsety=0-ty/2-1 end end if r > widthzone and shape.textEW ~= "0px" and not string.match(row.param1 or "","noline") then makeLineTo(result, row.gridx+1, row.gridy-1, row.gridx+row.dx+offsetx, row.gridy+row.dy+offsety, shape.textEW, shape.textES,shape.textEC,1) end if row.posType and row.posType=='mark-line' then if row.gridx2 and row.gridy2 then makeLineTo(result, row.gridx, row.gridy, row.gridx2, row.gridy2, tostring(row.mlWidth)..'px', row.mlStyle, getColor(shape.textEC),row.mlGap) -- debugmsg('makeLineTo line drawn from '..row.code..' with width '..tostring(row.mlWidth)..'px '..row.mlStyle..' and color '..getColor(shape.textEC)) end end if w ~= 0 then if shape.shape=='itriangle' then shape.shape='triangle' shape.shapeSize=tostring(w)..'px'..tostring(w/2)..'px' end if shape.shape=="triangle" then if shape.outlineWidth ~= "0px" then makeTriangle(result,row,shape,true,false) -- larger triangle to give the outline, if required end makeTriangle(result,row,shape,false,tshape) -- smaller triangle to fit over the top elseif shape.shape=="square" or shape.shape=="box" or shape.shape=='panel' then makeSquare(result,row,shape,tshape) elseif shape.shape=="circle" or shape.shape=="ellipse" then makeCircle(result,row,shape,tshape) elseif string.find(shape.shape,'image:')==1 then makeImage(result,row,shape) elseif shape.shape=="rulea" or shape.shape=='rule' then makeRuleA(result,row,shape) elseif shape.shape=="curvea" or shape.shape=="curvec" then makeCurveA(result,row,shape) else -- use a pathshape clipPath if shape.outlineWidth ~= "0px" then makeClipPath(result,row,shape,true,false) -- larger path-shape to give the outline, if required end makeClipPath(result,row,shape,false,tshape) end end if row.ppwidth and row.ppwidth>0 then makePhotoPanel(result,row,shape) end if shape.shape=='panel' and row.labelText then makePanelText(result, row, shape) else if row.dotTag and row.dotTag ~= "" then -- there is a dotTag if (row.dx==0 and row.dy==0) and w>0 then -- it is on the dot so if dotsize is not 0 any label is ignored makeTextItem(result, row, shape, align, tlink, '<b>'..row.dotTag..'</b>', 1) else if row.labelText and row.labelText~='' then -- tag and label both used makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.labelText, 0) else makeTextItem(result, row, shape, align, '', '<b>'..row.dotTag..'</b>', 1) makeTextItem(result, row, shape, align, tlink, row.dotTag, 2) -- tag is ouside the dot end end else if (row.labelText and row.labelText~='') then -- just the label. No tag makeTextItem(result, row, shape, align, tlink, row.labelText, 0) end end end if tlink and tlink~='' then table.insert(result,makeLinkBox(row.gridx, row.gridy,w+3,row.dotTag..' '..row.title,tlink)) end return table.concat(result) end local function getmapframecontent(args,use) local result, comma= {}, '' local propertyTable= {} local color=splitItem(string.lower( args['map-data-color'] or 'dark orange'), 2) if use == 'basemap' then table.insert(result, '[') propertyTable.stroke = "#000000" propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') propertyTable['stroke-opacity'] = tonumber(color[2] or '15')/100 else propertyTable.title = args['map-data-text'] or args['map-data'] or '' propertyTable.stroke = getColor(color[1]) propertyTable['stroke-width'] = tonumber(args['map-data-width'] or '6') propertyTable['stroke-opacity'] = tonumber(color[2] or '100')/100 end if args['map-data-inverse'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geomask', ids = args['map-data-inverse'], properties = {title = (args['map-data-text'] or ''), stroke = '#555555', fill = '#555555', ['fill-opacity'] = 0.1, ['stroke-width'] = 1, ['stroke-opacity'] = 0.5 } } table.insert(result, mapJson) comma=', ' end if args['map-data-heavy'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-heavy'], properties = { title = args['map-data-heavy'], stroke = propertyTable.stroke, ['stroke-width'] = 9, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.25 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data-light'] then local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data-light'], properties = { title = args['map-data-light'], stroke = propertyTable.stroke, ['stroke-width'] = 3, ['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.25 } } table.insert(result, comma .. mapJson) comma=', ' end if args['map-data'] then propertyTable['stroke-opacity'] = propertyTable['stroke-opacity'] or 0.1 local mapJson = mw.text.jsonEncode{ type = 'ExternalData', service = 'geoline', ids = args['map-data'], properties = propertyTable } table.insert(result, comma .. mapJson) comma=', ' end if args['map-wdqs'] then -- inserts a SPARQL wdqs query to find geopoints, geoshapes etc from wikidata local mapSparql = mw.text.jsonEncode{ type = 'ExternalData', service = args['map-wdqs-type'] or 'geopoint', query = args['map-wdqs'], } table.insert(result, comma .. mapSparql) -- debugmsg('mapSparql = ' .. mapSparql) end if args['map-raw'] then table.insert(result, comma .. args['map-raw']) end if use=='basemap' then table.insert(result,']') end return table.concat(result) end --eg | minilocator=filename,bottom right,132px153px, 38%,60%, 22px local function makeLocatorMap (args, result) local miniFile,pos,itemlist,miniW,miniH, miniX,miniY,miniBox, miniBH if args['mini-locator'] then pos,miniFile=extractItem(args['mini-locator']) -- first item is filename, in quotes if it includes commas itemlist=splitItem(pos,5) -- put items in a table filename removed, position,WpxHpx, x%y%,box pos=itemlist[2] or 'right' miniW,miniH=getsize(itemlist[3]) miniX=tonumber(string.match(itemlist[4] or '0','[%d]+'))*miniW/100 miniY=tonumber(string.match(itemlist[5] or '0','[%d]+'))*miniH/100 miniBox=tonumber(string.match(itemlist[6] or '0','[%d]+')) miniBox=miniBox*miniW/100 miniBH=miniBox * maplist.height/maplist.width elseif args['mini-file'] then miniFile = args['mini-file'] pos=string.lower(args.minimap or 'right') if pos=='file' then pos='right' end miniW,miniH = tonumber(args['mini-width'] or 60), tonumber(args['mini-height'] or 60) -- find top left corner of locator miniBox,miniBH=tonumber(args['minimap-boxwidth'] or '0'),0 -- firm up pos offsets for dot (with % or not) and boxsize if any, miniX, miniY=args['minipog-gx'],args['minipog-gy'] if not miniX then miniX=tonumber(args['minipog-x'] or '0') else miniX=tonumber(miniX)*tonumber(miniW)/100 end if not miniY then miniY=tonumber(args['minipog-y'] or '0') else miniY=tonumber(miniY)*tonumber(miniH)/100 end if args['minipog-gx'] then miniBox=miniBox*miniW/100 end miniBH=miniBox * maplist.height/maplist.width else return end local miniTop,miniLeft=0,1 if not string.find(pos,'top') then --only use top left, as link box is in top right or put bottom left or right miniTop=maplist.height+2-miniH if string.find(pos,'right') then miniTop=miniTop-15 -- to avoid (c) line miniLeft=maplist.width-1-miniW end end table.insert(result,'<div style="position: absolute; outline-width: 1px; outline-style: solid; outline-color: white;') table.insert(result,'top: '..tostring(miniTop)..'px; left:'..tostring(miniLeft)..'px; width:'..tostring(miniW)..'px;' ) table.insert(result,'background-color:transparent; color:inherit;">[[File:'..miniFile..'|'..tostring(miniW)..'px|File:'..miniFile..']]</div>') if miniX and miniX>0 then if args['minipog-y'] then miniY=miniY/1.04 end if args['minipog-x'] then miniX=miniX/1.04 end if miniBox<1 then table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-3-6)..'px; left:'..tostring(miniX+miniLeft-3)..'px;') table.insert(result, 'width: 6px; background-color:transparent; color:inherit;">[[File:Red pog.svg|6px|link=]]</div>') else table.insert(result,'<div style="position: absolute; top: '..tostring(miniY+miniTop-miniBH/2)..'px; left:'..tostring(miniX+miniLeft-miniBox/2)..'px;') table.insert(result, 'width: '..tostring(miniBox)..'px; height:'..tostring(miniBH)..'px; outline:1px solid #AA1205; background-color:#AA120522; color:inherit;"></div>') end end end local function makeArcText(args,result,nval) local items, itemlist='',{} local arcText='' if args['arc'..nval] then items=convertCoords (args['arc'..nval]) items,arcText=extractItem(items) -- first item is text, in quotes if it includes commas itemlist=splitItem(items,9) -- put items in a table: text, lat,lon,size,color,angle,gap,radius,ellipse if itemlist[4]=='' then itemlist[4]='12' end itemlist[4]=string.match((itemlist[4] or '12'),"[%.%-?%d]+") if itemlist[5]=='' then itemlist[5]='grey' end itemlist[6]=string.match((itemlist[6] or '0'),"[%.%-?%d]+") itemlist[7]=string.match((itemlist[7] or '0'),"[%.%-?%d]+") itemlist[8]=string.match((itemlist[8] or '0'),"[%.%-?%d]+") end if args['arc-coord'..nval] then local itemTab=splitItem(convertCoordsTrad (args['arc-coord'..nval]),2) maplist.lat=tonumber(string.match(itemTab[1],"[%.%-?%d]+")) maplist.lon=tonumber(string.match(itemTab[2],"[%.%-?%d]+")) else maplist.lat=tonumber(string.match(args['arc-lat'..nval] or itemlist[2] or '0',"[%.%-?%d]+")) maplist.lon=tonumber(string.match(args['arc-lon'..nval] or itemlist[3] or '0',"[%.%-?%d]+")) end local arcX,arcY=maptogrid(maplist,6) arcText = args['arc-text'..nval] or arcText local fontSize =tonumber(args['arc-text-size'..nval] or itemlist[4] or '12') local textColor=getColor(string.gsub(args['arc-text-color'..nval] or itemlist[5] or 'grey','[%s]+','') ) local arcAngle= tonumber((args['arc-angle'..nval]) or (itemlist[6]) or '45')-90 local arcRadius =tonumber(args['arc-radius'..nval] or itemlist[8] or '0.05') local arcGap = tonumber(args['arc-gap'..nval] or itemlist[7] or '1')* ( ( math.sin(8-math.rad(arcRadius))^8 )+0.4 )*( ( fontSize+6 )/15 ) arcRadius=450*arcRadius*0.75 local ellipseFactor=tonumber(args['ellipse-factor'..nval] or itemlist[9] or '1') local arcRotate =arcAngle+90 if arcGap<0 then arcRotate=arcAngle-90 end local latF=arcY - fontSize + (0-(math.sin(math.rad(arcAngle))) * arcRadius) local lonF=arcX - fontSize +(0-(math.cos(math.rad(arcAngle))) * arcRadius) local step=1 for codepoint in mw.ustring.gcodepoint( arcText ) do -- block step=1,#arcText do table.insert(result,'<div style="position: absolute;') local posY=tostring(round( (latF + (math.sin(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius)) *ellipseFactor,2))..'px;' local posX=tostring(round( (lonF + (math.cos(math.rad(arcAngle+((step-1)*arcGap))) * arcRadius))/ellipseFactor,2))..'px;' table.insert(result,' top: '..posY..' left: '..posX..' transform: rotate( '..tostring(round(arcRotate +((step-1)*arcGap)),2)..'deg);') table.insert(result,'width:'..tostring(fontSize*2)..'px; text-align: center; background-color:transparent; color: '..textColor..';') table.insert(result,'vertical-align: baseline; font-size: '..tostring(fontSize)..'px;">'..mw.ustring.char(codepoint)..'</div>') step=step+1 end end local function makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) local item={} itemdescription=stripdivs(itemdescription or '') local templon=lon if lon > 180 then templon=lon-360 end --for hemisphere+ or -1 dots if lon < -180 then templon=lon+360 end -- use 'real' coordinates for geohack label, while retaining shifted coords for plot if itemcolor=='transparent' then itemcolor='white' end itemcolor=getColor(itemcolor) -- ensure no opacity, which breaks maplink if string.find(itemcolor,'#')==1 and #itemcolor>7 then itemcolor=string.sub(itemcolor,1,7) end table.insert(item, '{ "type": "Feature", "properties": {') table.insert(item, ' "title": "'..itemtitle..'",') table.insert(item, ' "description": "'..itemdescription) table.insert(item, ' ([https://geohack.toolforge.org/geohack.php?params='..tostring(lat)..';'..tostring(templon)) table.insert(item, '_dim:2000 '..tostring(lat)..','..tostring(templon)..'])",') table.insert(item, ' "marker-symbol": "-number-'..string.gsub(group,'%W','')..'", "marker-size": "medium", "marker-color": "'..itemcolor..'" },') table.insert(item, ' "geometry": {"type": "Point", "coordinates": ['..tostring(lon)..','..tostring(lat)..'] } }') return table.concat(item) end local function makeLegendBox(result,args) local legend ={} local line,count, maxWidth='',1,8 local item line,item=extractItem(args.legendBox or '') local a='' for c in item:gmatch('.') do a=a..(c:gsub('%^','<br>') or c) end legend.Text = a -- convert hats to line breaks line=splitItem(line,6) -- (text, size, poition,background color, text/outline color, param options) legend.Size=line[2] or '150px80px1px' legend.Pos=line[3] or '10px10px' legend.Background=line[4] or 'beigeground' legend.Color=line[5] or 'darkbrown' legend.Param= line[6] or '' local argnum,legendCount,titleHeight='1',1,0 if (legend.Text and legend.Text~='') then titleHeight=15+(13.4*(select(2, string.gsub(legend.Text,"<br>", "")))) end local legendLine,legendGroup,legendY={},{},{} while args['legendItem'..argnum] do -- assign legendLine, legendGroup, legendY for each dot line,legendLine[legendCount] = extractItem(args['legendItem'..argnum] or '') line=splitItem(line,3) legendGroup[legendCount]=line[2] or argnum if shapeList[legendGroup[legendCount]] then maxWidth=math.max(tonumber(string.match(shapeList[legendGroup[legendCount]].shapeSize or '10','[%d]+')),maxWidth) else maxWidth=math.max(tonumber(string.match(shapeList['1'].shapeSize or '10','[%d]+')),maxWidth) end maxWidth=maxWidth+1 if line[3] then legendY[legendCount]=tonumber(string.match(line[3],'[%d]+')) else legendY[legendCount] = 3+maxWidth*(legendCount-1)+titleHeight -- if (legend.Text and legend.Text~='') then legendY[legendCount]=legendY[legendCount]+15 end end legendCount=legendCount+1 argnum=tostring(legendCount) end local w,h,r=getsize(legend.Size or '') local x,y=getsize(legend.Pos or '') local div=mw.html.create ('div') div:css('position', 'absolute') div:css('outline', '1px solid'..getColor(legend.Color)) if r~=0 then div:css('border-radius',tostring(r).."px") end div :css('top', y.."px") :css('left', x.."px") :css('width', w.."px") :css('height', h.."px") :css('line-height','105%') :css('background-color', getColor(legend.Background) ) :css('color','inherit') if not string.find(legend.Param,'noshadow') then div:css('box-shadow', '2px 2px 4px #33203335') end div:tag( 'div' ) :css('position', 'absolute') :css('top','1px') :css('left', (w/2).."px") :css('width',(w-8)..'px') :css('text-align', 'center') :css('color', getColor(legend.Color)) :css('transform', 'translateX(-50%)') :css('font-size','11px') :wikitext(legend.Text) :done() for lct=1,legendCount-1 do --local t=legendGroup[lct] local shape=shapeList[legendGroup[lct]] or shapeList['1'] local row={} row.gridx=3+maxWidth/2 row.gridy=(legendY[lct] or 0) + 5 if shape.shape=='image:' then row.gridy=row.gridy+6 end row.dx=0 row.dy=0 local legendShape= getshapetable(row,shape) div:wikitext(legendShape) :tag( 'div' ) :css('position', 'absolute') :css('top',(legendY[lct] or 0)..'px') :css('left', (maxWidth+6)..'px') :css('width', (w-maxWidth-6)..'px') :css('text-align', 'left') :css('line-height','103%') :css('color', getColor(legend.Color)) :css('font-size','10px') :wikitext(legendLine[lct]) :done() end div:allDone() table.insert(result,tostring(div)) end function p._main ( args ) local result={} local frame=mw.getCurrentFrame() local dotTable={} local magVal,scaleVal = args.magnify,'' local origH,origW=maplist.height,maplist.width if magVal then --set up the values needed to magnify the top-right portion of the map magVal=tonumber(string.match(magVal or '0',"[%.%-?%d]+")) or 1 if magVal>1 and magVal <=2 then maplist.height= round(maplist.height/magVal,0) maplist.width=round(maplist.width/magVal,0) scaleVal='transform: scale('..magVal..') translateY('..tostring((origH-maplist.height)/2)..'px);' else magVal=1 end --debugmsg('magVal='..magVal..', Magnify: H='..maplist.height..', ('..origH..'), W='..maplist.width..', ('..origW..'), scale='..scaleVal) --debugmsg('top: '..tostring(((origW-maplist.width))/2)..'px;right: '..tostring(0-((origW-maplist.width))/2)..'px; (alt top: '..tostring(((origH-maplist.height))/2)..'px;)') end -- set up the three nested div boxes (plus an extra if centered) to put the map plus title/caption area, in an appropriate frame on the page if args.float=='center' or args.float=='centre' then table.insert(result,'<div class="center"><div class="thumb tnone">') elseif args.float=='left' then table.insert(result,'<div class="thumb tleft">') else table.insert(result,'<div class="thumb tright">') end table.insert(result,'<div class="thumbinner" style="position: relative; top: 0px; right: 0px; width: '..(args.width or "400")..'px;">') if args.title then table.insert(result,'<div class="center" style="font-weight:bold">'..args.title..'</div>') end if magVal and magVal >1 and magVal<=2 then table.insert(result,'<div class="thumbinner noresize" style="display:block; position: relative; outline:0px; border:0px; padding:0px; background-color:transparent; color:inherit;') table.insert(result,'top: 50%; right: '..tostring(0-((origW-maplist.width))/2)..'px; ') table.insert(result,'height: '..origH..'px; width: '..maplist.width..'px; '..scaleVal..'">') else magVal=1 table.insert(result,'<div class="thumbinner noresize" style="position: relative; outline:0px; border:0px; padding:0px;') table.insert(result,'top: 0px; right: 0px; ') table.insert(result,'height: '..maplist.height..'px; width: '..maplist.width..'px ">') end -- Create the basemap using mapframe local mapframecontent=getmapframecontent(args,'basemap') table.insert(result, frame:extensionTag{ name ='mapframe', content=mapframecontent, args={width=tostring(maplist.width), height=tostring(maplist.height), zoom=tostring(maplist.zoom), longitude=tostring(maplist.lonbase), latitude=tostring(maplist.latbase), mapstyle=maplist.mapstyle, frameless=true } } ) --Add coverall box to block the unhelpful links from mapframe - which wouldn't include all the dots. Reinstate some links to osm and wikimedia table.insert(result,'<div style="position: absolute;width:'..tostring(maplist.width)..'px; height:'..tostring(maplist.height)..'px;') table.insert(result,'top:0px;left:0px;background-color:#FFFFFF00; color:inherit;"></div>') --Add replacent hover-links for OpenStreetMap and maps terms and conditions table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-13)..'px; width: 12px; height: 12px">') table.insert(result,'[[file:Transparent.svg|12px|link=https://www.openstreetmap.org/copyright|'..aboutOSM..']]</div>') table.insert(result,'<div style="position: absolute; top: '..tostring(maplist.height-18)..'px; left: '..tostring(maplist.width-110)..'px;') table.insert(result,'width: 12px; height: 12px background-color: transparent; color:inherit;">') table.insert(result,'[[file:Transparent.svg|12px| link=https://foundation.wikimedia.org/wiki/Policy:Maps_Terms_of_Use|'..termsOfUse..']]</div>') -- Add scale-line if not args.scalemark or args.scalemark~='0' then local top=maplist.height-42 local left=maplist.width-61-(tonumber(args.scalemark or '1')) local minipos=string.lower(args.minimap or '') -- scalemark gets pushed left if it would be behind the minimap if minipos~='' and not(string.find(minipos,'left') or string.find(minipos,'top') ) then local offset=tonumber(args.scalemark or '1')-tonumber(args['mini-width'] or '60') if offset<1 then left=maplist.width-61-tonumber(args['mini-width'] or '60') end end if maplist.width-left >216 then top=top+14 end -- shunt scaleline down if it is beyond the copyright stuff local scalek,scalem=getScale(maplist.zoom, maplist.latbase, magVal) local magReduce='' table.insert(result,"<div style=\"display:inline-block; position: absolute; background-color: #111111") table.insert(result,"; color:inherit; clip-path:path(nonzero, &#39;M0,8 l0,4 l20,0 l0,-4 l-0.3,0 l0,3.7 l-19.4,0 l0,-3.7 z&#39;) ") table.insert(result,"; width:20px") -- path is a 20px20px box, and then rescales if magVal==1 then table.insert(result,"; top:"..tostring(top-1).."px") table.insert(result,"; left:"..tostring(left+16).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5)..", "..tostring(1.5)..")") else -- shrink the scalemark to compensate for magnification table.insert(result,"; top:"..tostring(top-(magVal*0.28)).."px") table.insert(result,"; left:"..tostring(left+(16*(magVal*1.15))).."px") table.insert(result,"; height:20px; transform:scale("..tostring(2.5*(1/magVal))..", "..tostring(1.5*(1/magVal))..")") magReduce= 'scale('..tostring(1/magVal)..')' end table.insert(result,"\"></div>") table.insert(result,'<div style="position: absolute; top: '..tostring(top)..'px; left: '..tostring(left+47)..'px; font-size: 9.5px; line-height: 126%; width: fit-content;') table.insert(result,'color: #444433; background-color: transparent; text-align: right; transform: '..magReduce..' translateX(-100%);">'..scalek..'<br>'..scalem..'</div>') end --Set up the shapeList and dotList tables, to provide data to go on the map local sgNumbers,sgSortable={},{} --s1,s2 sgNumbers["1"]="1" if args.useFormatStyle and args.useFormatStyle=='shortstyle' then shapeList=ParseShapeTypes (shapeList,args,"1") for argindex,argv in pairs(args) do -- build a list of all the numbered sg's that have been used if string.find(argindex,"sg[a-f,n%d]+") == 1 then -- only look through the sga,sgb, sgc,sgd,sge,sgf and sgn args local x=string.match(argindex,"[%d]+") -- find its number if x and not sgNumbers[x] then sgNumbers[x]=x end -- only add if not already found end if string.find(argindex,"sg[a-f]H") == 1 and highlightNum then highlightOption=true end end for indx,sgnum in pairs(sgNumbers) do table.insert(sgSortable,sgnum) end table.sort(sgSortable,lessthan) -- put the list in a sortable form for k,v in pairs(sgSortable) do -- work through the sorted list, parsing each set of sg's in turn, from 1 upwards shapeList=ParseShapeTypes (shapeList,args,v) end if highlightOption==true then shapeList=ParseShapeTypes (shapeList,args,'H') end end local dotList,dotresult,dotItemTable,dotGroupList={},{},{},{} if (not args.useFormatStyle) or args.useFormatStyle=='standardstyle' then local dotmax=0 for indx in pairs(args) do if string.match(indx,'mark%-coord[%d]+') or string.match(indx,'lat[%d]+') then dotmax=math.max(dotmax, tonumber(string.match(indx,"[%d]+"))) end if (indx=='shapeH') or (indx=='shape-colorH') or (indx=='shape-outlineH') or (indx=='label-colorH') then highlightOption=true end end dotItemTable=tradstyleParseShapes(args,dotItemTable,dotmax) for argindex=1,dotmax do -- build a list of all the numbered coords or lat,lons that have been used local x=tostring(argindex) if args['mark-coord'..x] or (args['mark-lat'..x] and args['mark-lon'..x]) then sgNumbers[x]=x -- add it to the list end end else for indx in pairs(args) do if string.match(indx,'dot[%d]+') then table.insert(dotList,indx) end --add the index name for dot1, dot2 etc to dotList end table.sort(dotList,morethan) for indx,dotName in pairs(dotList) do dotresult=ParseData(args,string.match(dotName,'[%d]+') ) -- using each dot number, assign the settings for each dot to a dotresult item line table.insert(dotItemTable,dotresult) -- and store that item line within the dotItemTable end end for arcVal = 65,91 do -- check through args looking for any arcs local arcLetter=string.char(arcVal) if args['arc-text'..arcLetter] or args['arc'..arcLetter] then makeArcText(args,result,arcLetter) end end local dotdivs='' local ddots=0 if dotItemTable[1] then local ddots=(dotItemTable[1].lat or 0)+(dotItemTable[1].lon or 0) end local latbase = tonumber(maplist.latbase) local lonbase = tonumber(maplist.lonbase) if not latbase then latbase = maplist.lat or 0 end if not lonbase then lonbase = maplist.lon or 0 end local fgroup = 'F' .. tostring(latbase + lonbase + ddots) local FullscreenList={} local addcomma='' for i,dotitem in pairs(dotItemTable) do -- working throug each dot item, merge the dot and shape values into a full set of css text local dotgroup= dotitem.group or "0" if dotitem.posType=='mark-line' and dotitem.markDest then --find destination xy values for any mark-lines for n,v in pairs(dotItemTable) do if v.code == dotitem.markDest then dotitem.gridx2=v.gridx dotitem.gridy2=v.gridy break end end end local qtype=dotitem.group -- find which shape group each dot has been assigned --debugmsg('dotgroup='..qtype..', sg='..(sgNumbers[qtype] or 'nil')..' , shapeList='..shapeList[qtype].shape) if not sgNumbers[qtype] then qtype="0" end --shapeList[dotitem.group] will give access to the shape values for that dot if highlightNum==dotitem.code and highlightOption==true and shapeList['H'] then table.insert(result, getshapetable(dotitem,shapeList['H'])) else table.insert(result, getshapetable(dotitem,shapeList[qtype])) -- Add the actual css instructions for each dot end if shapeList[dotgroup] and not string.find((dotitem.param1 or ''),'nomap') then -- only add if not excluded with 'nomap' labelText local ftext='' if dotitem.dotTag~='' and not string.match(dotitem.labelText or '','[%d]') then ftext=stripdivs(dotitem.dotTag or '')..' <br>' end if (dotitem.labelText ~= ftext) and dotitem.dotLink =='' then ftext=ftext..' '..stripdivs(dotitem.labelText)..'<br>' end if (dotitem.dotLink) and (dotitem.dotLink ~='') then ftext=ftext..dotitem.dotLink..'<br>' end if dotitem.imageName then ftext=ftext..'[[File:'..dotitem.imageName..'|250px]]' end table.insert(FullscreenList,1, makeFullscreenItem (string.gsub(ftext,"[\n]+"," "), dotitem.info,dotitem.lat,dotitem.lon,fgroup,shapeList[dotgroup].shapeColor)..addcomma ) addcomma=', ' end -- makeFullscreenItem (itemtitle,itemdescription,lat,lon,group,itemcolor) -- Always add to start of list, to reverse the sequence, and separate with commas except for first item, which is now at the end end if args.legendBox then makeLegendBox(result,args) end if args.minimap or args['mini-locator'] then makeLocatorMap(args,result) end -- add tag link and details for fullscreen version addcomma='' if (mapframecontent or '[]') ~= '[]' then addcomma=',' end mapframecontent=getmapframecontent(args,'fullscreen') local contentstart='[ '..mapframecontent..addcomma..'{ "type": "FeatureCollection", "features": [ ' --extra features after first square bracket local contentend=' ] } ]' table.insert(result, '<div style="position: absolute;top: 9px;left: '..tostring(maplist.width-34)..'px">') table.insert(result, '<div style="color: white; opacity:100; font-size: 19px; font-weight:normal; text-align: left;">') table.insert(result, frame:extensionTag{ name ='maplink', content=contentstart..table.concat(FullscreenList)..contentend, args={zoom=tostring(maplist.zoom+1), class='no-icon', frameless='1', latitude=tostring(maplist.latbase), longitude=tostring(maplist.lonbase), --add invisble 'en-spaces' for tooltip text='<div title="'..fullscreenlinktext..'">&nbsp;&nbsp;&nbsp;</div>'} } ) table.insert(result,'</div></div>') -- end of maplink ----- -- add closing div for main map table.insert(result,'</div>') -- collate caption material to go in the outer div class local autocaption=string.lower(args['auto-caption'] or 'no') local autoOff=autocaption:match("(%w+)(.*)") -- select the first word in autocaption and see if it is a negativeAnswer) if args.caption or not negativeAnswer[autoOff] then table.insert(result,'<div class="thumbcaption" style="text-align:left">') if args.caption then table.insert(result,args.caption) end end local columns=tonumber(autoOff:match("[%d]+") or '1') if columns>1 then columns=round(maplist.width/(columns*17), 0) end -- convert from em to px for historical reasons --for k in pairs(dotList) do capchk=capchk..(args["dotlink"..k] or '') end local capchk=nil local captionList = {} for key, value in pairs(dotItemTable) do -- only add an autocaption line if there is both a dotTag and a dotLink line available and it is not marked as nolist if value.dotTag and value.dotTag~='' and (not string.find(value.param1 or '','nolist')) and string.gsub(value.dotLink or '',"%s+","")~='' then table.insert(captionList, {key = key, value = value}) capchk=true end end if capchk and (not negativeAnswer[autoOff]) then table.sort(captionList, function (a,b) return lessthan(string.match(a.value.dotTag,'[%w]+'), string.match(b.value.dotTag,'[%w]+')) end) local myDivision = string.gsub((args.toggletext or toggletext), "%s+", "") if string.find(autocaption,'collaps') then table.insert(result,'<hr><div class="mw-customtoggle-'..myDivision..'">'..(args.toggletext or toggletext)..'</div>') if string.find(autocaption,'collapsed') then table.insert(result,'<div class="mw-collapsible mw-collapsed" id="mw-customcollapsible-'..myDivision..'">') else table.insert(result,'<div class="mw-collapsible" id="mw-customcollapsible-'..myDivision..'">') end end if string.find(autocaption, 'columns=') then columns=string.match(autocaption,'[%d]+',string.find(autocaption, 'columns=') ) end table.insert(result,'<div class="mw-collapsible-content" style="column-count:'..columns..'; column-rule:solid 1px;text-align:left;padding-top:5px">') table.sort(dotList,lessthan) local nval,ngrp='','0' for k,v in pairs(captionList) do nval=string.match(v.value.dotTag,'[%w]+') -- find the first alphanumeric item in the dotTag ngrp=v.value.group or '0' if v.value.dotLink and v.value.dotLink~='' and nval and nval~='' then local c1,c2 if nval==args.highlight then c1,c2=checkColors(shapeList['H'].shapeColor) else c1,c2=checkColors(shapeList[ngrp].shapeColor) end table.insert(result,'<div style="display:inline-block;line-height:110%;vertical-align:middle; padding:1px 4px;border-radius:8px;border: 0.5px solid black;') table.insert(result,'background-color:'..c1..';color:'..c2..';font-size:88%;font-weight:bold">'..nval..'</div> '..v.value.dotLink..'<br>') end end table.insert(result,'</div>') -- end for caption-content div if string.find(autocaption,'collaps') then table.insert(result,'</div>') end -- end for toggle frame end if args.caption or not negativeAnswer[autoOff] then table.insert(result,'</div>') end -- end for whole caption frame table.insert(result,'</div></div>') -- outer two frames if args.float == 'center' or args.float=='centre' then table.insert(result,'</div>') end if args['show-new-format'] == 'hints' then -- provide a 'format hint panel' in the 'Preview Box' local w="<small>Below are some template hints for the 'sga' compressed version, " w=w..'{{tl|OSM Location dots}}. It can use these, instead of the more verbose {{tl|OSM Location map}} parameter format. ' w=w..'Data is divided between a "ShapeGroup" and the "Dots", so that a single shapeGroup can be used for multiple dots on the map. ' previewMsg(w..'(nb. the "group" value can be the number or an assigned name of a shapeGroup)</small>') w='<small>{{tl|OSM Location dots}}: | dot(n)=group,lat,lon,dotTag | ' w=w..'dotlink=link/tooltip | dotlabel=label,position,dx,dy,param1,info | dotpic=filename <br>' w=w.."(nb. param1 options include 'nolink' 'nolist' 'nomap' 'hemisphere-1' 'hemisphere+1', 'noline' - quotes not required, separate with spaces).<br>" w=w..'| sga = Shape,Sizepx,Color,Angledeg | sgb= OutlineWidth,Color,Style | sgc=TextSize,Color,Angle,bold italic <br>' w=w..'| sgd=TagSizepx,Color,Spacer,Angledeg | sge=LineWidth,Color,Style | sgf=TextSpacingpx,LineHeight%,Outlinepx,backgroundColor<br>' w=w..'| sgn=Name (optional, to assign a meaningful name) | sgp=Parent (can be the name or number of the parent shapeGroup. ' previewMsg(w..'Each shapegroup will inherit values from a parent, stretching back to "sga1" and its default values.)</small>') if #pmsg then local dbg={} for i,x in pairs(pmsg) do table.insert(dbg,x..'<br>') end dbg=mw.addWarning(table.concat(dbg)) table.insert(result, dbg) end end if args.coordtest then debugmsg(mw.text.nowiki(args.coordtest)) end for i,x in pairs(msg) do table.insert(result, x..'<br>') end return table.concat(result) end function p.main(frame) local args = getArgs(frame) local itemTab={} maplist.width=tonumber(args.width) or 400 maplist.height=tonumber(args.height) or 300 if args.coord then itemTab=splitItem(convertCoordsTrad (args.coord),2) maplist.latbase=itemTab[1] maplist.lonbase=itemTab[2] else maplist.lonbase=tonumber(args.lon) or 5 maplist.latbase=tonumber(args.lat) or 0 end maplist.zoom=tonumber(args.zoom) or 1 visibleLinks=args.showlinks highlightNum=args.highlight if args.nolabels=='1' then maplist.mapstyle='osm' else maplist.mapstyle='osm-intl' end return p._main(args) end return p ofjcmcbg4ar2hjutxobomnsg2e3ukvq Szablóna:OSM Location map 10 13246 204467 2026-07-05T15:38:01Z HaxelKaszëba 17492 Ùsôdzonô nowô starna "{{#invoke:OSM Location map|main|useFormatStyle={{{useFormatStyle|standardstyle}}} <noinclude> |label1=Annotated maps of anywhere in the world |mark-coord1={{coord|0|0}}|mark-size1=10|label-pos1=top</noinclude> }} {{Main other|[[Category:Articles containing OSM location maps]]}}<noinclude> [[Category:OSM Location map templates]] [[Category:Pages with disabled graphs]] </noinclude>" 204467 wikitext text/x-wiki {{#invoke:OSM Location map|main|useFormatStyle={{{useFormatStyle|standardstyle}}} <noinclude> |label1=Annotated maps of anywhere in the world |mark-coord1={{coord|0|0}}|mark-size1=10|label-pos1=top</noinclude> }} {{Main other|[[Category:Articles containing OSM location maps]]}}<noinclude> [[Category:OSM Location map templates]] [[Category:Pages with disabled graphs]] </noinclude> bn3ie51w472y054e66h3l43q8qawo4d Zelenin 0 13247 204477 2026-07-05T18:59:36Z Terrus38 14026 Terrus38 przeniós starnã [[Zelenin]] do [[Zeleniô]]: Błędna pisownia tytułu: Zeleniô je zgòdą z http://skarbnicakaszubska.pl/uchwaa-nr-6-2010/ 204477 wikitext text/x-wiki #PATRZ [[Zeleniô]] mtvxz9w4tq4nqm7tz8hlnzz9izih0ye Kategòrëjô:Meksyk 14 13248 204510 2026-07-06T11:55:41Z Iketsi 3254 N 204510 wikitext text/x-wiki {{Commons}} [[Kategòrëjô:Nordowô Amerika]] l015vcgh0nkb7mdmh8waqx17j6ebdzl