چون مطلب طولانی شد، خلاصه بگم: این جا درباره ی تجربه ی ساخت نقشه «فارسی در جهان» توی گوگل مپ و درباره ی این که این نقشه چیه حرف می زنم، ساختار فایل kml رو می گم و در نهایت کدهایی رو که نوشتم توضیح می دم. کار نهایی رو می تونید این جا ببینید.
دو هفته پیش یه دفعه یه ایده ای به ذهنم رسید که فکر می کردم پیاده سازیش خیلی آسون باشه اما خب سخت و زمانبر بود (البته برای من).
ایده این بود که بر اساس «شناختنامه زبان فارسی در جهان» (که در کنار «سند راهبردی زبان فارسی» تالیف شده) یه نقشۀ فارسی در جهان توی «نقشه های من گوگل مپ» درست کنم. این شناختنامه چیه؟ یه شناختنامه که بنیاد سعدی تهیه کرده و توش می گه کشورای مختلف نسبت به این که مخاطب آموزش زبان فارسی باشند، توی چه دسته ای جا می شن. مثلن راهبرد بنیاد سعدی (به عنوان متولی گسترش زبان فارسی در جهان) در مورد کشورای «فارسی زبان» با کشورای «عرب زبان» یا «شبه قاره» یا «آمریکای لاتین» متفاوته.
ایده ی اصلی این بود اما واقعیتش می خواستم کار با نقشه های گوگل رو یاد بگیرم. قبلن «نقشۀ طرح ترافیک تهران» و «نقشۀ مناطق تهران» رو توی گوگل مپ دیده بودم و خوشم اومده بود.
اما وقتی شروع به کار کردم، دیدم خیلی چیزاست که نمی دونم و تازه باید برم یاد بگیرم! دو نوع چالش توی این قضیه داشتم. یکی چالشهای محتوایی و یکی چالشهای اجرایی و برنامهنویسی.
قبل از این که برم سراغ توصیف این چالش ها بذارید یه کوچولو درباره ی اون سند راهبردی حرف بزنم. توی این سند، کشورای جهان بر حسب این که چطوری مخاطب زبان فارسی هستند، به ده حوزه تقسیم شده:
این سند تا جایی که می دونم به صورت عمومی منتشر نشده و من هم به اون دسترسی نداشتم و کار رو بر مبنای «شناخت نامه» پیش بردم. احتمالن از طریق ارسال ایمیل یا تماس با بنیاد سعدی می تونید شناخت نامه رو به دست بیارید. به هر حال اصل تقسیم بندی اینه اما اسم همه کشورها توی این شناخت نامه نیومده. اسم یه سری کشورها و مناطقی که از دید تهیه کنندگان کتاب مهم بوده آورده شده و وضعیت زبان فارسی توی این کشورها توصیف شده. چیزی که باعث شد توی این پروژه به مشکل بربخورم.
خب حالا بریم سراغ چالش اول: اسم کشورها. همون طور که گفتم توی این شناخت نامه اسم یه سری «مناطق» اومده بود. در بعضی موارد اسم یا تقسیم بندی قدیمی کشورها ذکر شده بود. مثلن یوگسلاوی سابق. من باید یه فهرست قابل اطمینان از کشورهای جهان پیدا می کردم که بتونم تقسیم رو بر اساس اون انجام بدم. برای من قابل اتکاترین فهرست، «فهرست کشورهای عضو سازمان ملل متحد» بود. از همین فهرست (که فعلن شامل 193 تا کشوره) استفاده کردم و فقط اسم «اسرائیل» رو (چون مخاطب این سند رسمیِ جمهوری اسلامی نیست) حذف کردم و اسم «فلسطین» رو (با این که جزو کشورهای عضو سازمان ملل نیست) اضافه کردم. اسم انگلیسی کشورها رو هم همون چیزی گذاشتم که توی سایت سازمان ملل هست. مثلن «کره شمالی» اسم رسمیش «جمهوری دموکراتیک خلق کره» است و من از همین اسم رسمی استفاده کردم. اسم های فارسی رو هم از این صفحه ویکی پدیا گرفتم.
مسئله ی بعدی ارتباط دادن کشورها با اون مناطق بود. این تقسیم بندی یه کم متنوعه و دسته های مختلفش با هم لزومن همخوانی ندارند. مثلن «کشورهای عرب زبان» یک اتحادیه ی رسمی با 22 کشور عضو دارند و می شه همین کشورا رو توی این دسته بندی وارد کرد اما «آفریقای زیر صحرا» یه تقسیم بندی جغرافیاییه و بخشی از کشور «سودان» یا «چاد» جزو این دسته بندی جغرافیایی هست و بخشی نیست. بنابراین در این جور موارد باید با اغماض رفتار می کردم (مثلن کل سودان و چاد رو جزو آفریقای زیرصحرا به حساب آوردم).
از طرف دیگه بعضی موارد گنگ بود. مثلن «ازبکستان» در این کتاب جزو «کشورهای فارسی زبان» محسوب شده با این که زبان این کشور «ازبکی» و جزو زبان های ترکی است اما تهیه کنندگان کتاب با توجه به تاثیر زیاد فارسی روی ازبکی و نزدیکی فرهنگ دو کشور ایران و ازبکستان، این کشور رو هم جزو «کشورهای فارسی زبان» محسوب کردند [این مورد در به روز رسانی 24 بهمن 99 تغییر کرد]. مبنای من هم همین شناخت نامه بود و تغییری توی این موارد ندادم.
مسئله ی بعدی مناطقی بود که سر مالکیتشون دعواست. چون من می خواستم مرز مناطق رو روی نقشه جهان نشون بدم، مهم بود که چیزی از قلم نیفته. «جنوبگان» الان جزو قلمرو هیچ کشوری محسوب نمی شه و بنابراین توی نقشه با هیچ رنگی مشخصش نکردم. جزایر مختلفی هم که به هر نحوی به یه کشور مرتبطند (چه این که رسمن جزو قلمرو اون کشور باشند یا فقط از نظر سیاسی وابسته به اون کشور باشند)، جزو قلمرو همون کشور و منطقه محسوب کردم. بنابر این مثلن جزایر جنوب آفریقا با این که از نظر جغرافیایی به آفریقای زیرصحرا نزدیکتر هستند یا گویان فرانسه با این که جزو قارۀ آمریکای جنوبی است، چون جزو قلمروهای فرادریایی فرانسه محسوب می شن جزو منطقه «اروپای غربی» دسته بندی شدند.
چالش جدید (حل نشده، به روزرسانی 22 تیر 99)
دوست عزیزی (@handicraftsouvenir) تذکر داد که رنگ کشورهای شمال ایران به گونه ای است که دریای خزر پوشانده شده است. وقتی بررسی کردم متوجه شدم، در مورد دریای خزر، مرزهای دریایی هم مشخص شده! یعنی سهم ایران، ترکمنستان، آذربایجان، قزاقستان و روسیه از این دریا جزو مرزهای این کشورها تعیین شده است. با توجه به این که تا جایی که می دانم هنوز بر سر مرزهای این دریاچه بین این 5 کشور بحث است، فکر نمی کنم این مرزبندی صحیح باشد. بنابر این باید به وسیله ای این مرزها را تصحیح کنم اما هنوز بلد نیستم.
سخت تر از چالش های محتوایی، چالش های اجرایی بودند. اولین مسئله این بود که من نمی دونستم چه جوری و با چه فایلی می شه مرزها رو توی نقشه گوگل مشخص کرد. یه راهش این بود که با ابزار خود نقشه گوگل، خط بکشم و یه چهار ضلعی درست کنم. اما فهمیدم یه نوع فایل به نام kml وجود داره که گوگل ساخته در واقع یه فایل xmlه. با این فایل می شه نقاط جغرافیایی دوبعدی و سه بعدی رو مشخص کرد و رنگ بهشون داد و خیلی کارای دیگه. نسخه ی فشرده شده ی kml، می شه kmz. نمونه ی یه فایل ساده ی kml این جوریه:
<?xml version="1.0" encoding="UTF-8"?> <kml xmlns="http://www.opengis.net/kml/2.2"> <Document> <Style id="my-style"> <IconStyle> <color>ffff0000</color> <scale>1.0</scale> </IconStyle> <LineStyle> <color>ffff0000</color> <width>1</width> </LineStyle> <PolyStyle> <color>9900f2ff</color> </PolyStyle> <BalloonStyle> <text>$[name]: $[description]</text> </BalloonStyle> </Style> <Placemark> <snippet/> <name>Area</name> <description></description> <styleUrl>#my-style</styleUrl> <MultiGeometry> <Polygon> <outerBoundaryIs> <LinearRing> <coordinates> 35.669018156,33.212173238 35.6811092190001,33.2445091110001 ... 35.669018156,33.212173238 </coordinates> </LinearRing> </outerBoundaryIs> </Polygon> </MultiGeometry> </Placemark> </Document> </kml>
یه توضیح مختصری درباره ی این ساختار بدم (توضیح کامل رو می تونید توی مستندات kml ببینید).. خط اول که مشخص می کنه این یه نوع فایل xmlه. بعد تگ kml شروع می شه و بعد تگ Document که کل فایل ما توش قرار می گیره. بعد Style رو داریم که برای مشخص کردن شکل ظاهری و یه سری چیزای دیگه استفاده می شه. این استایل یه آی دی داره که بعدن برای این که بگیم کدوم بخش کار ما چه شکلی داشته باشه به این آی دی می تونیم ارجاع بدیم. من این جا آی دی my-style رو انتخاب کردم. توی این استایل شکل آیکون، شکل خط، شکل چندضلعی و شکل بالون توضیحات مشخص شده. نکته ای که وجود داره اینه که ساختار رنگ توی kml متفاوت با rgbه! در واقع اول شفافیت تصویر (آلفا) مشخص می شه بعد رنگ آبی، قرمز و سبز. یعنی: AABBGGRR. دلیلش رو هم نمی دونم. توی استایل بالون می تونیم مشخص کنیم که توی توضیحات شکل چه چیزایی بیاد. من این جا گفتم از تگ name و description برای پرکردن بالون توضیحات استفاده کن.
بعد از استایل می رسیم به بخش اصلی کار که همون Placemark باشه. توی این بخش می تونیم یک یا چند محدوده رو مشخص کنیم و براش اسم و توضیحات بنویسیم (همون چیزایی که قراره توی بالون توضیحات بیاد). محدوده رو با MultiGeometry مشخص می کنیم. یه Placemark می تونه شامل یک یا چند محدوده باشه. توی coordinates میایم مرزهای محدوده رو با طول و عرض جغرافیایی مشخص می کنیم. انگار داریم یه چندضلعی می کشیم و می گیم کدوم نقطه رو به کدوم نقطه وصل کن. طبیعتن وقتی می خوایم محدوده مشخص کنیم، نقطه ی اول و نقطه ی آخرمون باید یکی باشند.
این خلاصه ای درباره ی kml بود.
پس کاری که من باید می کردم پیدا کردن فایل های kml کشورها بود (چون رسم چنین چیزی کار سخت و زمانبریه و طبیعتن یکی قبل از من این کار رو انجام داده). اولین جایی که پیدا کردم سایت GADM بود. این سایت انواع نقشه ها و اطلاعات جغرافیایی همه ی کشورای جهان رو داره و برای خیلی از کشورا زیرمجموعه های اون کشور رو هم داره. مثلن برای ایران نقشه های این سایت در سطح استان و شهرستان هم هست. برای هر کشور می شه فایل kmz رو دانلود کرد. پس برای کار من کافی بود نقشه ی هر کشور رو با فرمت kmz دانلود کنم و بعد بر اساس مناطق مشخص شده، کشورها رو با هم ترکیب کنم و به یه نقشه ی واحد برای هر منطقه برسم.
مشکل این بود که این سایت خیلی دقیقه و حجم نقشه هاش زیاد! از اون طرف فهمیدم که هر فایلی که قراره توی نقشه های گوگل آپلود بشه نباید بیشتر از 5 مگابایت حجم داشته باشه اما فایل های من برای بعضی مناطق تا 150 مگابایت رسیده بود (تازه نسخه ی فشرده شده. توی گوگل مپ باید فایل kml بذارم نه نسخه ی فشرده شده ی kmz)! اولین راهکاری که به ذهنم رسید فشرده کردن این فایل ها بود. بنابراین با استفاده از نرم افزار ویندوزی گوگل ارث، فایل های kmz کشورهای هر منطقه رو با هم ترکیب کردم و بعد به kml تبدیل کردم و سعی کردم با کمک سایت هایی که برای فشرده کردن فایل های kml وجود دارند، این فایل ها رو فشرده کنم. اما باز هم به حجم زیر 5 مگابایت نرسیدم.
بنابراین دنبال نقشه های کم حجم تر کشورهای جهان با فرمت kml گشتم و به این نقشه رسیدم. این نقشه در واقع کل کشورهای جهان در قالب یک فایل kml است اما وقتی یه کم مستندات این نوع فایل رو خوندم متوجه شدم فرق زیادی با xml نداره و به راحتی می شه placemark های هر کشور رو جدا کرد. کل نقشه زیر 1 مگابایت بود و به نظر میومد مشکلم رو حل می کنه اما متاسفانه حجم کم به معنی خیلی نادقیق بودن نقشه بود! برای بعضی مناطق یه چهارضلعی خیلی ساده کشیده شده بود و خیلی از جزئیات مرزها نادیده گرفته شده بود. اگه می خواستیم به کل نقشه جهان نگاه کنیم این مسئله زیاد مشکل درست نمی کرد اما من دنبال یه نقشه ی دقیق تر بودم که بشه روش زوم کرد و به مشکل برنخورد.
بنابر این بیشتر گشتم و به این نقشه رسیدم. این نقشه دقیق تر بود و کل حجمش هم 7 مگابایت بود. با کد ساده ای که نوشتم، تگ های Multigeometry هر کشور رو توی یه فایل جدا قرار دادم (من این کار رو با php و با استفاده از simplexml_load_file انجام دادم. طبیعتن هر زبانی ابزارهای خودش رو برای کار با xmlها داره):
<?php $xml = simplexml_load_file('source_file.xml'); foreach ($xml->Document->Folder->Placemark as $placemark) { foreach ($placemark->ExtendedData->SchemaData->SimpleData as $simpleData) { if ($simpleData['name'] == "COUNTRY") { $countryName = $simpleData; } } file_put_contents("destination/$countryName.xml", "<kml>".$placemark->MultiGeometry->asXML()."</kml>"); }
اما مشکل این بود که توی این فایل جزیره ها تگ های جدا داشتند و اسم کشورها هم دقیقن منطبق بر اسم رسمی سازمان ملل نبود. این موارد رو دستی درست کردم. جزیره ها رو تک تک سرچ کردم و فهمیدم متعلق به کدوم کشور هستند و به فایل همون کشور اضافه کردم. اسم کشورها رو هم بر اساس اسم رسمی تصحیح کردم.
حالا باید کشورهای هر منطقه رو با هم ترکیب می کردم. برای این کار اول اسم فارسی و انگلیسی همه ی مناطق و کد رنگ هر کدوم رو (که بر اساس ساختار kml باید برعکس rgb می شدند) توی یه فایل csv گذاشتم:
آسیای مرکزی,Central-Asia,992265f2 اروپای غربی,Western-Europe,99319d6c اروپای شرقی,Eastern-Europe,9977a4d6 آمریکای شمالی و استرالیا,North-America-and-Oceania,99b08e00 آمریکای لاتین,Latin-America,99b681d3 آفریقای زیر صحرا,Sub-Saharan-Africa,99c1c1c2 کشورهای عربی,Arab-world,9900f2ff کشورهای فارسی زبان,Persian-Countries,99241ced شبه قاره,Indian-subcontinent,99e1ce94 آسیای شرقی,East-Asia,998c00ec
بعد اسم فارسی و انگلیسی همه کشورهای هر منطقه رو توی فایل های جدا قرار دادم. مثلن برای کشورهای فارسی زبان فایل Persian-Countries.csv رو درست کردم و این جوری اسم ها رو توش گذاشتم:
Afghanistan,افغانستان Iran (Islamic Republic of),ایران Tajikistan,تاجیکستان Uzbekistan,ازبکستان
و بعد این کارها رو کردم:
ساختار این کد چنین چیزی شد:
$areas_file = fopen("areas.csv", "r"); while (($area_line = fgetcsv($areas_file)) !== FALSE) { $area_fa_name = $area_line[0]; $area_en_name = $area_line[1]; $color = $area_line[2]; $ballon_text = "$area_en_name: $[name] - $area_fa_name: $[description]" //3: xmlwriter for area... //4: $countries_file = fopen("area_countries/$area_en_name.csv", "r"); while (($country_line = fgetcsv($countries_file)) !== FALSE) { $country_en_name = $country_line[0]; $country_fa_name = $country_line[1]; //5: xmlwriter for country... //6: $country_file = simplexml_load_file("countries_multigeometry/$country_en_name.xml"); $multi_geometry = $country_file->MultiGeometry->asXML(); } }
و در نهایت فایل های ساخته شده رو توی نقشه های من گوگل مپ آپلود کردم.
کد این پروژه رو توی گیتهاب گذاشتم. فایل های multigeometry هر کشور رو هم توی یه مخزن جدا گذاشتم. نقشه ی نهایی رو هم که از این جا می تونید ببینید.
به روز رسانی 24 بهمن 99: با توجه به توضیحات یکی از کارشناسان بنیاد سعدی، نام «آسیای مرکزی» به «آسیای مرکزی و قفقاز» تغییر یافت و «ازبکستان» به بخش «آسیای مرکزی و قفقاز» منتقل شد (البته این تغییرات رو هنوز در مخزن گیتهاب اعمال نکردم).