{"isTrainingKarta":false,"mapColorPalette":[],"socketTokenTTLSeconds":1209600,"commonAnalyticsProperties":{"feature_flags":["ff_all_column_index","ff_create_layers","ff_growth_enterprise_trials","ff_photos_in_comments","ff_server_side_filtering"],"workspace_id":"ff6d0ea7-abed-415f-9b75-a55f0b24c418","workspace_name":"pum","workspace_plan":"education","workspace_age_days":172,"native_app_device_model":null,"native_app_os_version":null,"native_app_platform":null,"native_app_version":null,"platform_source":"web"},"defaultControlsMode":null,"embedConfig":null,"layerProcessingEmailSubscriptions":[],"curated_layers_enabled":true,"canCreateMapsSomewhere":false,"current_user":{},"showBasemapLabels":true,"layerGroups":[{"id":"e0e3b996-d164-46d3-9943-484136c506d4","name":"Perevalka Vazhneyshikh Gruzov 2024","visible":false,"description":"","created_at":"2025-10-27T18:14:05","layers":[{"source_id":null,"tile_max_zoom":null,"source_has_custom_query":false,"tile_url":"https://us1.data-pipeline.felt.com/vectortile/22271fa0-85cb-4366-885e-093300004453/{z}/{x}/{y}.pbf{?attributes,layer,query}","raster_preview_url":null,"html_popup_threads":[],"raster_details":null,"stats":[{"aggregation":null,"binLevel":null,"count":26,"count_distinct":25,"name":"Млн т","type":"TEXT","values":{"Азов":1,"Ванино":1,"Варандей":1,"Владивосток":1,"Восточный":1,"Высоцк":1,"Де-Кастри":1,"Кавказ":1,"Калининград":1,"Мурманск":1,"Находка":1,"Новороссийск":1,"Посьет":1,"Представлены данные по портам с перевалкой указанных грузов, превышающей 2 млн тонн":1,"Пригородное":1,"Приморск":1,"Россия в целом":1,"Ростов-на-Дону":1,"Сабетта":1,"Санкт-Петербург":1,"Тамань":2,"Туапсе":1,"Указанные порты":1,"Усть-Луга":1,"Шахтёрск":1}}],"initial_stroke_color":null,"modified_at":"2025-10-27T18:49:06","remote_data_url":null,"visible":true,"feature_url":"https://us1.data-pipeline.felt.com/onefeature/22271fa0-85cb-4366-885e-093300004453/{feature}.geojson{?skip_geometry,zoom_level}","stac_url":null,"initial_fill_color":null,"processing_time_seconds":42,"bounding_box":{"coordinates":[[[-75.19349670410156,7.986539840698242],[140.76914978027344,7.986539840698242],[140.76914978027344,71.27178192138672],[-75.19349670410156,71.27178192138672],[-75.19349670410156,7.986539840698242]]],"crs":{"properties":{"name":"EPSG:4326"},"type":"name"},"type":"Polygon"},"style":{"config":{"categoricalAttribute":"Млн т","categories":{"count":10,"type":"top"},"labelAttribute":["Field1"],"otherOrder":"below","showOther":true},"label":{"color":"auto","fontSize":13,"fontStyle":"Normal","fontWeight":500,"haloColor":"auto","haloWidth":1,"justify":"auto","letterSpacing":0,"lineHeight":1.2,"maxLineChars":10,"maxZoom":24,"minZoom":1,"offset":[8,8],"padding":2,"placement":"auto","textTransform":"none"},"legend":{"displayName":"auto"},"paint":{"color":"@catPalette4","intensity":0.5,"opacity":0.72,"size":3,"strokeColor":"auto","strokeWidth":1},"type":"categorical","version":"2.3.1"},"geometry_type":"Point","column_selections":[{"type":"full_address","column":"Млн т"}],"parsed_size_bytes":151552,"index_json_url":"https://us1.data-pipeline.felt.com/upload/f99baea2-30a4-546f-964e-90820000554c.json","runCause":"reinterpret","z_order":1,"legendDisplay":"default","stats_url":"https://us1.data-pipeline.felt.com/stats/22271fa0-85cb-4366-885e-093300004453.json","legend_items":[{"id":"Perevalka Vazhneyshikh Gruzov 2024-data-item","visible":true}],"status":"completed","legendVisibility":"show","pipeline_version":"0.49.13795","scheduled_refresh_frequency":null,"sql_query_threads":[],"geomatch_metadata":null,"layer_name":"parsed","errorMessage":null,"progress":100,"errorType":null,"debug_layer_compaction":null,"geocoder_metadata":{"full_matches":24,"no_matches":2,"results_url":"https://us1.data-pipeline.felt.com/geocoding-results/22271fa0-85cb-4366-885e-093300004453.csv"},"last_processed_at":"2025-10-27T18:16:43","max_zoom":18,"ready_for_immediate_export":true,"external_refresh_frequency_ms":null,"created_at":"2025-10-27T18:14:05","hash_url":"https://us1.data-pipeline.felt.com/hash/{hash}","normalized":{"filename":"Perevalka_vazhneyshikh_gruzov_2024.xlsx","feature_id_field":null,"filetype":"GeoPackage","layername":"Лист1"},"subtitle":"","min_zoom":0,"next_processing_state":null,"has_download_url":true,"curated_layer":false,"edit_version":null,"id":"c51f6d74-853d-4dfc-b5d4-b20c245dd98b","name":"Perevalka Vazhneyshikh Gruzov 2024","created_by":"Nikolay Zolotov","h3_geomatched_level":null,"raster_colors":null,"pipeline_dataset_id":"22271fa0-85cb-4366-885e-093300004453","centroids_layer_name":null,"hideFromLegend":false,"scheduled_refresh_status":null,"source_dataset_id":null,"h3_levels":[8,12,16,21,21,23,23,23,23,23,23,23,23,23,23,23],"html_popup_source":null,"excerpt_url":"https://us1.data-pipeline.felt.com/excerpt/22271fa0-85cb-4366-885e-093300004453.json","data_last_updated_by_user_at":"2025-10-27T18:16:43","table":{"name":"parsed","columns":[{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/22271fa0-85cb-4366-885e-093300004453/0/1.json{?query}","count":23,"count_distinct":23,"max":23,"median":12,"min":1,"name":"Field1","stats_url":"https://us1.data-pipeline.felt.com/stats/22271fa0-85cb-4366-885e-093300004453/0.json{?query}","type":"INTEGER","values":{"1":1,"10":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"null":3}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/22271fa0-85cb-4366-885e-093300004453/1/1.json{?query}","count":26,"count_distinct":25,"name":"Млн т","stats_url":"https://us1.data-pipeline.felt.com/stats/22271fa0-85cb-4366-885e-093300004453/1.json{?query}","type":"TEXT","values":{"Азов":1,"Ванино":1,"Варандей":1,"Владивосток":1,"Восточный":1,"Высоцк":1,"Де-Кастри":1,"Кавказ":1,"Калининград":1,"Мурманск":1,"Тамань":2}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/22271fa0-85cb-4366-885e-093300004453/2/1.json{?query}","count":12,"count_distinct":12,"max":188.1,"median":13.9,"min":0.9399255715045188,"name":"Уголь","stats_url":"https://us1.data-pipeline.felt.com/stats/22271fa0-85cb-4366-885e-093300004453/2.json{?query}","type":"REAL","values":{"0.9399255715045188":1,"12.2":1,"12.3":1,"13.9":1,"14.7":1,"18.1":1,"2.4":1,"24.5":1,"3.7":1,"33.2":1,"null":14}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/22271fa0-85cb-4366-885e-093300004453/3/1.json{?query}","count":6,"count_distinct":6,"max":42.5,"median":2.7,"min":0.9552941176470587,"name":"Удобрения","stats_url":"https://us1.data-pipeline.felt.com/stats/22271fa0-85cb-4366-885e-093300004453/3.json{?query}","type":"REAL","values":{"0.9552941176470587":1,"16.2":1,"19.7":1,"2.0":1,"2.7":1,"42.5":1,"null":20}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/22271fa0-85cb-4366-885e-093300004453/4/1.json{?query}","count":8,"count_distinct":8,"max":74.8,"median":5.7,"min":0.8288770053475937,"name":"Зерно","stats_url":"https://us1.data-pipeline.felt.com/stats/22271fa0-85cb-4366-885e-093300004453/4.json{?query}","type":"REAL","values":{"0.8288770053475937":1,"12.9":1,"2.3":1,"26.5":1,"4.8":1,"5.7":1,"74.8":1,"9.8":1,"null":18}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/22271fa0-85cb-4366-885e-093300004453/5/1.json{?query}","count":7,"count_distinct":7,"max":55.5,"median":11.3,"min":0.8810810810810812,"name":"Контейнеры","stats_url":"https://us1.data-pipeline.felt.com/stats/22271fa0-85cb-4366-885e-093300004453/5.json{?query}","type":"REAL","values":{"0.8810810810810812":1,"11.3":1,"13.1":1,"15.5":1,"2.0":1,"55.5":1,"7.0":1,"null":19}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/22271fa0-85cb-4366-885e-093300004453/6/1.json{?query}","count":11,"count_distinct":11,"max":267.5,"median":29.6,"min":0.9917757009345795,"name":"Нефть","stats_url":"https://us1.data-pipeline.felt.com/stats/22271fa0-85cb-4366-885e-093300004453/6.json{?query}","type":"REAL","values":{"0.9917757009345795":1,"29.6":1,"3.1":1,"33.6":1,"43.8":1,"44.7":1,"5.1":1,"6.1":1,"9.0":1,"90.3":1,"null":15}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/22271fa0-85cb-4366-885e-093300004453/7/1.json{?query}","count":12,"count_distinct":12,"max":131.1,"median":7.7,"min":0.8871090770404272,"name":"Нефтепродукты","stats_url":"https://us1.data-pipeline.felt.com/stats/22271fa0-85cb-4366-885e-093300004453/7.json{?query}","type":"REAL","values":{"0.8871090770404272":1,"16.9":1,"19.0":1,"2.4":1,"5.0":1,"6.3":1,"6.6":1,"7.7":1,"8.2":1,"9.9":1,"null":14}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/22271fa0-85cb-4366-885e-093300004453/8/1.json{?query}","count":5,"count_distinct":5,"max":36.3,"median":10.2,"min":0.931129476584022,"name":"СПГ","stats_url":"https://us1.data-pipeline.felt.com/stats/22271fa0-85cb-4366-885e-093300004453/8.json{?query}","type":"REAL","values":{"0.931129476584022":1,"10.2":1,"2.3":1,"21.3":1,"36.3":1,"null":21}}],"row_count":26,"content_url":"https://us1.data-pipeline.felt.com/table/22271fa0-85cb-4366-885e-093300004453/{page}.json{?query}"},"is_spreadsheet":true,"semantic_columns":[]}],"created_by":"Nikolay Zolotov","modified_at":"2025-10-27T18:56:07","user_id":"8a91b5c4-9820-450d-8b1b-12e3bdab0e25","index_json_url":"https://us1.data-pipeline.felt.com/upload/f99baea2-30a4-546f-964e-90820000554c.json","max_zoom":18,"subtitle":null,"z_order":5,"isCollapsed":false,"hideFromLegend":false,"errorMessage":null,"thumbnailUrl":null,"progress_percent":100,"visibilityInteraction":"checkbox","created_at_unix_time_ms":1761588845000,"duplicatedFromId":null,"errorType":null,"legendVisibility":"show","published_to_project_ids":null,"renderAsLayer":true},{"id":"70047595-006b-44d7-9474-c64f69602c38","name":"Deepseek Csv 20251025 0Bd3Aa Txt","visible":false,"description":"","created_at":"2025-10-27T18:34:58","layers":[{"source_id":null,"tile_max_zoom":1,"source_has_custom_query":false,"tile_url":"https://us1.data-pipeline.felt.com/vectortile/7638c04a-29dc-5c32-b392-5bbf00004453/{z}/{x}/{y}.pbf{?attributes,layer,query}","raster_preview_url":null,"html_popup_threads":[],"raster_details":null,"stats":[{"aggregation":null,"binLevel":null,"count":47,"count_distinct":7,"name":"cargo","type":"TEXT","values":{"Зерно":6,"Контейнеры":5,"Нефтепродукты":10,"Нефть":9,"СПГ":3,"Уголь":10,"Удобрения":4}}],"initial_stroke_color":null,"modified_at":"2025-10-27T18:38:20","remote_data_url":null,"visible":true,"feature_url":"https://us1.data-pipeline.felt.com/onefeature/7638c04a-29dc-5c32-b392-5bbf00004453/{feature}.geojson{?skip_geometry,zoom_level}","stac_url":null,"initial_fill_color":null,"processing_time_seconds":6,"bounding_box":{"coordinates":[[[20.5,42.66669845581055],[142.7667236328125,42.66669845581055],[142.7667236328125,71.2667007446289],[20.5,71.2667007446289],[20.5,42.66669845581055]]],"crs":{"properties":{"name":"EPSG:4326"},"type":"name"},"type":"Polygon"},"style":{"config":{"categoricalAttribute":"cargo","categories":["Нефтепродукты","Уголь","Нефть","Зерно","Контейнеры","СПГ","Удобрения"],"labelAttribute":["port"],"showOther":false},"label":{"color":"auto","fontSize":13,"fontStyle":"Normal","fontWeight":500,"haloColor":"auto","haloWidth":1,"justify":"auto","letterSpacing":0,"lineHeight":1.2,"maxLineChars":10,"maxZoom":23,"minZoom":23,"offset":[8,8],"padding":2,"placement":"auto","textTransform":"none"},"legend":{"displayName":"auto"},"paint":{"color":["#77AADD","#EE8866","#EEDD88","#FFAABB","#99DDFF","#BBCC33","#77AADD"],"opacity":0.9,"size":3,"strokeColor":"auto","strokeWidth":1},"type":"categorical","version":"2.3.1"},"geometry_type":"Point","column_selections":[{"type":"lng","column":"lon"},{"type":"lat","column":"lat"}],"parsed_size_bytes":143360,"index_json_url":"https://us1.data-pipeline.felt.com/upload/a2214495-3cb4-45f1-9ad8-16000000554c.json","runCause":"upload","z_order":1,"legendDisplay":"default","stats_url":"https://us1.data-pipeline.felt.com/stats/7638c04a-29dc-5c32-b392-5bbf00004453.json","legend_items":[],"status":"completed","legendVisibility":"show","pipeline_version":"0.49.13795","scheduled_refresh_frequency":null,"sql_query_threads":[],"geomatch_metadata":null,"layer_name":"parsed","errorMessage":null,"progress":100,"errorType":null,"debug_layer_compaction":null,"geocoder_metadata":null,"last_processed_at":"2025-10-27T18:34:58","max_zoom":18,"ready_for_immediate_export":true,"external_refresh_frequency_ms":null,"created_at":"2025-10-27T18:34:58","hash_url":"https://us1.data-pipeline.felt.com/hash/{hash}","normalized":{"filename":"deepseek_csv_20251025_0bd3aa.txt","feature_id_field":null,"filetype":"Comma Separated Value (.csv)","layername":"deepseek_csv_20251025_0bd3aa"},"subtitle":"","min_zoom":0,"next_processing_state":null,"has_download_url":true,"curated_layer":false,"edit_version":null,"id":"50549639-be61-47cb-a7bd-0501b6858017","name":"Deepseek Csv 20251025 0Bd3Aa","created_by":"Nikolay Zolotov","h3_geomatched_level":null,"raster_colors":null,"pipeline_dataset_id":"7638c04a-29dc-5c32-b392-5bbf00004453","centroids_layer_name":null,"hideFromLegend":false,"scheduled_refresh_status":null,"source_dataset_id":null,"h3_levels":[7,9,13,18,20,22,22,22,22,22,22,22,22,22,22,22],"html_popup_source":null,"excerpt_url":"https://us1.data-pipeline.felt.com/excerpt/7638c04a-29dc-5c32-b392-5bbf00004453.json","data_last_updated_by_user_at":"2025-10-27T18:34:58","table":{"name":"parsed","columns":[{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/7638c04a-29dc-5c32-b392-5bbf00004453/0/1.json{?query}","count":47,"count_distinct":22,"name":"port","stats_url":"https://us1.data-pipeline.felt.com/stats/7638c04a-29dc-5c32-b392-5bbf00004453/0.json{?query}","type":"TEXT","values":{"Владивосток":2,"Восточный":3,"Высоцк":2,"Кавказ":2,"Мурманск":3,"Находка":2,"Новороссийск":4,"Санкт-Петербург":3,"Тамань":3,"Туапсе":4,"Усть-Луга":4}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/7638c04a-29dc-5c32-b392-5bbf00004453/1/1.json{?query}","count":47,"count_distinct":7,"name":"cargo","stats_url":"https://us1.data-pipeline.felt.com/stats/7638c04a-29dc-5c32-b392-5bbf00004453/1.json{?query}","type":"TEXT","values":{"Зерно":6,"Контейнеры":5,"Нефтепродукты":10,"Нефть":9,"СПГ":3,"Уголь":10,"Удобрения":4}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/7638c04a-29dc-5c32-b392-5bbf00004453/2/1.json{?query}","count":47,"count_distinct":44,"max":90.3,"median":11.3,"min":2.0,"name":"value","stats_url":"https://us1.data-pipeline.felt.com/stats/7638c04a-29dc-5c32-b392-5bbf00004453/2.json{?query}","type":"REAL","values":{"2.0":2,"2.3":2,"2.4":2,"2.7":1,"3.1":1,"3.7":1,"4.8":1,"5.0":1,"5.1":1,"5.7":1,"6.1":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/7638c04a-29dc-5c32-b392-5bbf00004453/3/1.json{?query}","count":47,"count_distinct":21,"max":142.7667,"median":37.7833,"min":20.5,"name":"lon","stats_url":"https://us1.data-pipeline.felt.com/stats/7638c04a-29dc-5c32-b392-5bbf00004453/3.json{?query}","type":"REAL","values":{"133.05":3,"28.3975":4,"28.5667":2,"28.6167":2,"30.3089":3,"33.0821":3,"36.6833":5,"37.7833":4,"39.066":4,"39.7167":2,"72.0667":2}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/7638c04a-29dc-5c32-b392-5bbf00004453/4/1.json{?query}","count":47,"count_distinct":22,"max":71.2667,"median":47.1167,"min":42.6667,"name":"lat","stats_url":"https://us1.data-pipeline.felt.com/stats/7638c04a-29dc-5c32-b392-5bbf00004453/4.json{?query}","type":"REAL","values":{"42.7333":3,"42.8167":2,"43.1155":2,"44.094":4,"44.7167":4,"45.1911":3,"45.35":2,"46.65":2,"59.6672":4,"59.9311":3,"68.9707":3}}],"row_count":47,"content_url":"https://us1.data-pipeline.felt.com/table/7638c04a-29dc-5c32-b392-5bbf00004453/{page}.json{?query}"},"is_spreadsheet":true,"semantic_columns":[]}],"created_by":"Nikolay Zolotov","modified_at":"2025-10-27T19:14:05","user_id":"8a91b5c4-9820-450d-8b1b-12e3bdab0e25","index_json_url":"https://us1.data-pipeline.felt.com/upload/a2214495-3cb4-45f1-9ad8-16000000554c.json","max_zoom":18,"subtitle":null,"z_order":6,"isCollapsed":false,"hideFromLegend":false,"errorMessage":null,"thumbnailUrl":null,"progress_percent":100,"visibilityInteraction":"checkbox","created_at_unix_time_ms":1761590098000,"duplicatedFromId":null,"errorType":null,"legendVisibility":"show","published_to_project_ids":null,"renderAsLayer":true}],"isAdminView":false,"mapbox_api_token":"pk.eyJ1IjoiZmVsdG1hcHMiLCJhIjoiY20wZndoenl1MTFrYzJxb2czemdpNGFvZCJ9.y7NzVOMNQNTC487xOYTk7Q","featureFlags":{"ff_extensions_use_next_sdk":false,"felt_is_up":true,"ff_contributor_permission":false,"ff_copy_paste_styles":false,"ff_create_layers":true,"ff_create_layers_single_select":false,"ff_growth_enterprise_trials":true,"ff_isr":false,"ff_mobile_alpha":false,"ff_more_components":false,"ff_photos_in_comments":true,"ff_server_side_filtering":true,"ff_use_our_tiles":false,"ff_wherobots":false},"mapFolderId":null,"mapProject":null,"mapBackgrounds":[],"elements":[],"mapDescription":null,"mapLinks":[],"currentTeamBannerAcknowledgement":{"plan":null,"plan_name":null,"acknowledged":true},"constraints":{"bounds":null,"maxZoom":null,"minZoom":null},"validGeoDataExtensions":[".json",".geojson"],"comments":{"threads":[],"users":{}},"defaultCoordinates":{"lat":37.807,"lng":-122.271},"referer":null,"loadedAt":1767009698,"ghostPresences":[],"workspaceBilling":{"plan_limits":{"processing_bytes":50000000000,"editor_limit":50,"member_limit":50,"contributor_limit":0,"storage_bytes":100000000000,"api_calls":50000,"map_views":100000},"limits_enforced":true,"next_monthly_cycle_start":1767225600000,"plan_data_usage":{"data_processing":0,"data_storage":468536821},"featureGroups":{"enterprise_geocoding":false,"dashboards":true,"cloud_sources":true,"app_development":true,"raster_infrastructure":true},"inGracePeriod":false},"isVirtualKarta":false,"customIcons":[],"pipeline":{"syncUploadMaxBytes":15360},"authors":{"8a91b5c4-9820-450d-8b1b-12e3bdab0e25":{"name":"Nikolay Zolotov"}},"shareUrl":"https://felt.com/map/Perevalka-vazhneyshih-gruzov-2024-g-zFUScKWMRXanasmtbMvI4C","checkerboardTiles":false,"defaultZoom":14.0,"mapTitle":"Перевалка важнейших грузов, 2024 г.","accessControl2":{"view":{"canDownloadLayers":false,"canDuplicate":false},"edit":{"canChangePublicSharingSettings":false,"canEditDuplicateSetting":false,"canInviteUsers":false,"canMove":false},"comments":{"canAttachPhotosToComments":false,"canCreateThread":false,"canDeleteAnyComment":false,"canDeleteOwnComment":false,"canEditOwnComment":false,"canExportComments":false,"canMoveAnyThread":false,"canMoveOwnThread":false,"canReplyToThread":false,"canResolveAnyComment":false,"canResolveOwnComment":false,"canTrackReadStatus":false,"canViewAllThreads":false},"teams":{"canAddSources":false,"canDeletePublishedLayer":false,"canPublishLayer":false},"kartaTeamMemberIds":[]},"editableByCurrentSession":false,"settings":{"path":"/map/Perevalka-vazhneyshih-gruzov-2024-g-zFUScKWMRXanasmtbMvI4C","url":"https://felt.com/map/Perevalka-vazhneyshih-gruzov-2024-g-zFUScKWMRXanasmtbMvI4C","mapDescription":null,"mapTitle":"Перевалка важнейших грузов, 2024 г.","viewers":{"allowExport":false,"dataTable":true,"defaultTableLayerId":null,"duplicateMap":true,"seeMapPresence":true}},"selectedDefaultBackgroundMode":"default","customViewport":null,"kartaTeamId":"ff6d0ea7-abed-415f-9b75-a55f0b24c418","satelliteMode":false,"amplitudeAnalyticsId":"d01926b917624c9f002b79f6016c7213","kartaChannel":"anonymous_map:cc551270-a58c-4576-a76a-c9ad6ccbc8e2","canPurchasePlan":true,"teamAdminEmails":[],"updatedAtUnixMs":1761598900000,"maxTileURLLength":2000,"flashMessages":[],"validMapImageMimeTypes":["application/pdf","application/postscript","application/x-photoshop","image/apng","image/avif","image/bmp","image/heic","image/jp2","image/jpeg","image/jpm","image/jpx","image/jxr","image/pict","image/pjpeg","image/png","image/svg+xml","image/vnd.adobe.photoshop","image/vnd.microsoft.icon","image/webp","image/x-icns","image/x-icon","image/x-pict"],"folderId":null,"isOwner":false,"mapId":"cc551270-a58c-4576-a76a-c9ad6ccbc8e2","teams":[],"sharing":{"publicAccess":"view_only"},"createMapParams":null,"resyncTimeoutMs":5250,"layerAttrConstraints":{"maxNameLength":300},"actions":[],"partnerBranding":null,"folderTree":[],"maxImageFileSizeBytes":20000000,"selectedBackgroundId":null,"sources":[],"mapTeamId":null,"onboardingNeeded":[],"mapUrls":{"export":"https://felt.com/map/export/cc551270-a58c-4576-a76a-c9ad6ccbc8e2","embed":"https://felt.com/embed/map/Perevalka-vazhneyshih-gruzov-2024-g-zFUScKWMRXanasmtbMvI4C","login":"/login/map/cc551270-a58c-4576-a76a-c9ad6ccbc8e2","appHomeForKarta":"/maps/contains/cc551270-a58c-4576-a76a-c9ad6ccbc8e2","dataTroubleshooting":"https://help.felt.com/upload-anything/troubleshooting","exportComments":"https://felt.com/map/export/cc551270-a58c-4576-a76a-c9ad6ccbc8e2/comments","signup":"/signup/map/cc551270-a58c-4576-a76a-c9ad6ccbc8e2","supportedFormats":"https://help.felt.com/upload-anything/files"},"urls":{"api":"/api","settings":"/users/settings","dashboard":"/maps","marketing":"/","newMap":"/map/new","newWorkspace":"/join","placeholderImage":"/images/placeholder-39c4259c68cef774b08a1a06e6626c95.png?vsn=d","canonicalPath":"/map/Perevalka-vazhneyshih-gruzov-2024-g-zFUScKWMRXanasmtbMvI4C","catchup":"/map/Perevalka-vazhneyshih-gruzov-2024-g-zFUScKWMRXanasmtbMvI4C/catchup","spritesBaseUrl":"https://app-assets.felt.com/sprites","spritesManifest":{"/sprites/msdf.json":"/sprites/msdf-0ed695ece9e09531d79ea4daa8255599.json","/sprites/msdf.png":"/sprites/msdf-235245f7cf195ff0eb39cf7a7b8fa6e9.png","/sprites/msdf@2x.json":"/sprites/msdf@2x-1e78f109cb6438c7e87da1f75344a7b7.json","/sprites/msdf@2x.png":"/sprites/msdf@2x-73817a584887afef4c1e0188642592ab.png","/sprites/sprite.json":"/sprites/sprite-964569eef729aa4773a354b47cee5d86.json","/sprites/sprite.png":"/sprites/sprite-819511448f6f2499deff86603ff78215.png","/sprites/sprite@2x.json":"/sprites/sprite@2x-24e211e07ce576bf48740474ed7d6547.json","/sprites/sprite@2x.png":"/sprites/sprite@2x-87cb20c28d14435142f4e32339e093dd.png"},"workspaceUsage":null},"widgets":[],"individualPermissions":[],"isAdmin":false,"allowedFeatures":{"enterprisePipelineExports":true,"commentsAttachedData":true,"workspaceLibrary":true,"editableLayers":true,"basicExports":true,"advancedExports":true,"sameDomainJoin":true,"embedActionCustomization":true,"basicPipelineExports":true,"enterpriseExports":true,"embeds":true,"mapActions":true,"sdk":true,"bucketSources":true,"createLayerFromColumns":true,"customIcons":true,"h3":true,"viewerExportData":true,"transformations":true,"iframePopups":true,"streamVectorTiles":true,"embedTokens":true,"extensions":true,"sources":true,"streamCogs":true,"usageDashboard":true,"widgets":true,"stacSource":true,"commentsAttachedMedia":true,"filters":true,"liveLayers":true,"uploadLayer":true,"updateEnterpriseSettings":true},"felt_version":"43","mapImages":[],"buildSha":"9daac66156c953dc5809c6bbbf4213b744eba3d7","scripts":[{"id":"4d176e36-96fc-4912-9b29-db097a7cc2a0","name":"Custom extension","threads":[{"id":"2fa46c59-c75a-4463-9512-473dac0ec0df","insertedAt":"2025-10-27T19:28:32.716227","scriptId":"4d176e36-96fc-4912-9b29-db097a7cc2a0"}],"content":"\n\n// Данные портов с координатами и грузооборотом\nconst portsData = {\n \"Мурманск\": { coords: [33.0833, 68.9667], cargo: { \"Уголь\": 12.3, \"Удобрения\": 2.7, \"Нефть\": 29.6 }},\n \"Усть-Луга\": { coords: [28.3167, 59.6833], cargo: { \"Уголь\": 41.8, \"Удобрения\": 16.2, \"Нефть\": 33.6, \"Нефтепродукты\": 34.3 }},\n \"Туапсе\": { coords: [39.0667, 44.1000], cargo: { \"Уголь\": 2.4, \"Удобрения\": 2.0, \"Зерно\": 2.3, \"Нефтепродукты\": 9.9 }},\n \"Тамань\": { coords: [36.7167, 45.2167], cargo: { \"Уголь\": 12.2, \"Зерно\": 5.7, \"Нефтепродукты\": 7.7 }},\n \"Владивосток\": { coords: [131.8742, 43.1056], cargo: { \"Уголь\": 14.7, \"Контейнеры\": 15.5 }},\n \"Посьет\": { coords: [130.7833, 42.6500], cargo: { \"Уголь\": 3.7 }},\n \"Находка\": { coords: [132.8736, 42.8147], cargo: { \"Уголь\": 18.1, \"Нефтепродукты\": 5.0 }},\n \"Восточный\": { coords: [133.0167, 42.7833], cargo: { \"Уголь\": 33.2, \"Контейнеры\": 7.0, \"Нефть\": 44.7 }},\n \"Ванино\": { coords: [140.2667, 49.0833], cargo: { \"Уголь\": 24.5 }},\n \"Шахтёрск\": { coords: [142.0833, 49.3667], cargo: { \"Уголь\": 13.9 }},\n \"Санкт-Петербург\": { coords: [30.3609, 59.8944], cargo: { \"Удобрения\": 19.7, \"Контейнеры\": 13.1, \"Нефтепродукты\": 6.6 }},\n \"Новороссийск\": { coords: [37.7697, 44.7208], cargo: { \"Зерно\": 26.5, \"Контейнеры\": 11.3, \"Нефть\": 90.3, \"Нефтепродукты\": 19.0 }},\n \"Азов\": { coords: [39.4167, 47.1000], cargo: { \"Зерно\": 4.8 }},\n \"Ростов-на-Дону\": { coords: [39.7342, 47.2357], cargo: { \"Зерно\": 9.8, \"Нефтепродукты\": 2.4 }},\n \"Кавказ\": { coords: [37.2167, 45.0333], cargo: { \"Зерно\": 12.9, \"Нефтепродукты\": 6.3 }},\n \"Калининград\": { coords: [20.4522, 54.7065], cargo: { \"Контейнеры\": 2.0 }},\n \"Варандей\": { coords: [58.0667, 68.8333], cargo: { \"Нефть\": 5.1 }},\n \"Сабетта\": { coords: [72.1167, 71.2500], cargo: { \"Нефть\": 6.1, \"СПГ\": 21.3 }},\n \"Приморск\": { coords: [28.6167, 60.3500], cargo: { \"Нефть\": 43.8, \"Нефтепродукты\": 16.9 }},\n \"Высоцк\": { coords: [28.5667, 60.6167], cargo: { \"Нефтепродукты\": 8.2, \"СПГ\": 2.3 }},\n \"Де-Кастри\": { coords: [140.2167, 51.4667], cargo: { \"Нефть\": 9.0 }},\n \"Пригородное\": { coords: [142.1333, 46.9667], cargo: { \"Нефть\": 3.1, \"СПГ\": 10.2 }}\n};\n\n// Цвета для типов грузов с эмодзи\nconst cargoColors = {\n \"Уголь\": { color: \"#2C2C2C\", emoji: \"⚫\" },\n \"Нефть\": { color: \"#8B4513\", emoji: \"🟤\" }, \n \"Нефтепродукты\": { color: \"#FF6B35\", emoji: \"🟠\" },\n \"Зерно\": { color: \"#F4D03F\", emoji: \"🟡\" },\n \"Удобрения\": { color: \"#58D68D\", emoji: \"🟢\" },\n \"Контейнеры\": { color: \"#3498DB\", emoji: \"🔵\" },\n \"СПГ\": { color: \"#9B59B6\", emoji: \"🟣\" }\n};\n\nlet pieChartElements = [];\n\n// Функция для расчета расстояния между двумя точками в метрах\nfunction calculateDistance(coord1, coord2) {\n const R = 6371000; // Радиус Земли в метрах\n const lat1 = coord1[1] * Math.PI / 180;\n const lat2 = coord2[1] * Math.PI / 180;\n const deltaLat = (coord2[1] - coord1[1]) * Math.PI / 180;\n const deltaLng = (coord2[0] - coord1[0]) * Math.PI / 180;\n\n const a = Math.sin(deltaLat/2) * Math.sin(deltaLat/2) +\n Math.cos(lat1) * Math.cos(lat2) *\n Math.sin(deltaLng/2) * Math.sin(deltaLng/2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));\n\n return R * c;\n}\n\n// Функция для проверки пересечения двух окружностей\nfunction checkCircleOverlap(center1, radius1, center2, radius2) {\n const distance = calculateDistance(center1, center2);\n const minDistance = radius1 + radius2 + 5000; // Добавляем 5км буфер\n return distance < minDistance;\n}\n\n// Функция для смещения координат порта в случае конфликта\nfunction resolvePositionConflict(originalCoords, conflictingPorts, radius) {\n const directions = [\n [0, 1], // север\n [1, 0], // восток\n [0, -1], // юг\n [-1, 0], // запад\n [1, 1], // северо-восток\n [1, -1], // юго-восток\n [-1, -1], // юго-запад\n [-1, 1] // северо-запад\n ];\n \n for (const direction of directions) {\n for (let distance = radius * 2.5; distance <= radius * 6; distance += radius * 0.5) {\n // Учитываем искажение проекции\n const latRadians = originalCoords[1] * Math.PI / 180;\n const cosLat = Math.cos(latRadians);\n \n const offsetLat = (distance * direction[1]) / 111000;\n const offsetLng = (distance * direction[0]) / (111000 * cosLat);\n \n const newCoords = [\n originalCoords[0] + offsetLng,\n originalCoords[1] + offsetLat\n ];\n \n // Проверяем, не пересекается ли новая позиция с существующими\n let hasConflict = false;\n for (const conflictPort of conflictingPorts) {\n if (checkCircleOverlap(newCoords, radius, conflictPort.coords, conflictPort.radius)) {\n hasConflict = true;\n break;\n }\n }\n \n if (!hasConflict) {\n return newCoords;\n }\n }\n }\n \n // Если не удалось найти свободное место, возвращаем исходные координаты\n return originalCoords;\n}\n\n// Функция для вычисления оптимальных позиций портов\nfunction calculateOptimalPositions() {\n const portPositions = [];\n \n // Сортируем порты по размеру (крупные размещаем первыми)\n const sortedPorts = Object.entries(portsData)\n .map(([name, data]) => ({\n name,\n originalCoords: data.coords,\n cargo: data.cargo,\n totalCargo: Object.values(data.cargo).reduce((sum, val) => sum + val, 0)\n }))\n .sort((a, b) => b.totalCargo - a.totalCargo);\n\n for (const port of sortedPorts) {\n const radius = Math.max(16000, Math.min(70000, port.totalCargo * 400));\n \n // Проверяем конфликты с уже размещенными портами\n const conflictingPorts = [];\n for (const placedPort of portPositions) {\n if (checkCircleOverlap(port.originalCoords, radius, placedPort.coords, placedPort.radius)) {\n conflictingPorts.push(placedPort);\n }\n }\n \n let finalCoords;\n if (conflictingPorts.length > 0) {\n finalCoords = resolvePositionConflict(port.originalCoords, conflictingPorts, radius);\n } else {\n finalCoords = port.originalCoords;\n }\n \n portPositions.push({\n name: port.name,\n originalCoords: port.originalCoords,\n coords: finalCoords,\n cargo: port.cargo,\n totalCargo: port.totalCargo,\n radius: radius\n });\n }\n \n return portPositions;\n}\n\n// Функция для создания координат сегмента круговой диаграммы с учетом проекции\nfunction createPieSegmentCoordinates(center, radiusMeters, startAngle, endAngle, segments = 30) {\n const coords = [[center[0], center[1]]]; // Начинаем с центра\n \n const angleStep = (endAngle - startAngle) / segments;\n \n // Учитываем искажение проекции на разных широтах\n const latRadians = center[1] * Math.PI / 180;\n const cosLat = Math.cos(latRadians);\n \n // Конвертируем радиус в градусы с учетом широты\n const radiusLat = radiusMeters / 111000; // градусы по широте\n const radiusLon = radiusMeters / (111000 * cosLat); // градусы по долготе с коррекцией\n \n for (let i = 0; i <= segments; i++) {\n const angle = (startAngle + i * angleStep) * Math.PI / 180;\n const x = center[0] + radiusLon * Math.cos(angle);\n const y = center[1] + radiusLat * Math.sin(angle);\n coords.push([x, y]);\n }\n \n coords.push([center[0], center[1]]); // Замыкаем в центре\n return [coords];\n}\n\n// Создание соединительных линий для смещенных портов\nasync function createConnectionLines(portPositions) {\n const connections = [];\n \n for (const port of portPositions) {\n const distance = calculateDistance(port.originalCoords, port.coords);\n \n // Создаем соединительную линию только если порт был смещен значительно (> 10км)\n if (distance > 10000) {\n try {\n const connectionLine = await felt.createElement({\n type: \"Path\",\n coordinates: [[port.originalCoords, port.coords]], // Исправлен формат координат\n name: `Связь с ${port.name}`,\n description: `Диаграмма смещена для избежания перекрытия`,\n color: \"#95A5A6\",\n strokeWidth: 1,\n strokeStyle: \"dashed\",\n strokeOpacity: 0.6,\n interaction: \"locked\"\n });\n connections.push(connectionLine);\n } catch (error) {\n console.log(`Ошибка создания линии связи для ${port.name}:`, error);\n }\n }\n }\n \n return connections;\n}\n\n// Создание круговых диаграмм как полигонов\nasync function createPieCharts() {\n console.log(\"Вычисление оптимальных позиций портов...\");\n const portPositions = calculateOptimalPositions();\n \n console.log(\"Создание круговых диаграмм...\");\n pieChartElements = [];\n \n // Создаем соединительные линии для смещенных портов\n try {\n const connections = await createConnectionLines(portPositions);\n pieChartElements.push(...connections);\n } catch (error) {\n console.log(\"Ошибка создания соединительных линий:\", error);\n }\n \n for (const portData of portPositions) {\n let currentAngle = 0;\n \n // Создаем сегменты для каждого типа груза\n for (const [cargoType, amount] of Object.entries(portData.cargo)) {\n const percentage = amount / portData.totalCargo;\n const segmentAngle = percentage * 360;\n \n if (segmentAngle > 3) { // Показываем сегменты больше 3 градусов\n const startAngle = currentAngle;\n const endAngle = currentAngle + segmentAngle;\n \n try {\n const segmentCoords = createPieSegmentCoordinates(\n portData.coords, \n portData.radius, \n startAngle, \n endAngle\n );\n \n const segmentElement = await felt.createElement({\n type: \"Polygon\",\n coordinates: segmentCoords,\n name: `${portData.name}: ${cargoType}`,\n description: `${amount} млн т (${(percentage * 100).toFixed(1)}% от общего объема порта)`,\n color: cargoColors[cargoType]?.color || \"#95A5A6\",\n fillOpacity: 0.85,\n strokeWidth: 0,\n strokeOpacity: 0,\n interaction: \"locked\"\n });\n \n pieChartElements.push(segmentElement);\n } catch (error) {\n console.log(`Ошибка создания сегмента ${cargoType} для ${portData.name}:`, error);\n }\n }\n \n currentAngle += segmentAngle;\n }\n }\n \n console.log(`Создано ${pieChartElements.length} элементов диаграмм`);\n \n // Обновляем данные портов с новыми координатами\n for (const portData of portPositions) {\n portsData[portData.name].displayCoords = portData.coords;\n }\n}\n\n// Создание GeoJSON данных для портов (только точки для базового слоя)\nfunction createPortsGeoJSON() {\n const features = [];\n \n for (const [portName, data] of Object.entries(portsData)) {\n const totalCargo = Object.values(data.cargo).reduce((sum, val) => sum + val, 0);\n \n const feature = {\n type: \"Feature\",\n geometry: {\n type: \"Point\", \n coordinates: data.coords\n },\n properties: {\n name: portName,\n totalCargo: totalCargo,\n ...data.cargo,\n cargoTypes: Object.keys(data.cargo).join(\", \"),\n topCargo: Object.entries(data.cargo).reduce((a, b) => a[1] > b[1] ? a : b)[0]\n }\n };\n features.push(feature);\n }\n \n return {\n type: \"FeatureCollection\",\n features: features\n };\n}\n\n// Создание базового слоя с портами\nasync function createPortsLayer() {\n const geojson = createPortsGeoJSON();\n \n const result = await felt.createLayersFromGeoJson({\n name: \"Российские порты - базовый слой\",\n source: {\n type: \"geoJsonData\",\n data: geojson\n },\n description: \"Базовые точки портов России\",\n geometryStyles: {\n Point: {\n version: \"2.3.1\",\n type: \"simple\",\n config: { labelAttribute: [\"name\"] },\n paint: {\n color: \"#1E88E5\",\n size: 4,\n strokeColor: \"#FFFFFF\",\n strokeWidth: 1,\n opacity: 0.8\n },\n label: {\n color: \"#1A237E\",\n haloColor: \"#FFFFFF\", \n haloWidth: 2,\n fontSize: [10, 12],\n minZoom: 6\n },\n legend: {\n displayName: \"Порты России\"\n }\n }\n }\n });\n \n return result;\n}\n\n// Создание панели управления\nasync function createControlPanel() {\n const panelId = await felt.createPanelId();\n \n await felt.createOrUpdatePanel({\n panel: {\n id: panelId,\n title: \"🚢 Порты России - Круговые диаграммы\",\n body: [\n {\n type: \"Text\",\n content: \"**Грузооборот российских портов 2024**\\n\\nКруговые диаграммы показывают структуру перевалки грузов. Размер диаграммы пропорционален общему объему порта.\\n\\n*Диаграммы автоматически смещаются во избежание перекрытий.*\"\n },\n {\n type: \"Divider\"\n },\n {\n type: \"Text\",\n content: \"**Легенда цветов:**\"\n },\n {\n type: \"Text\",\n content: Object.entries(cargoColors)\n .map(([type, data]) => `${data.emoji} **${type}**`)\n .join('\\n')\n },\n {\n type: \"Divider\"\n },\n {\n type: \"ButtonRow\",\n label: \"Управление диаграммами:\",\n items: [\n {\n type: \"Button\",\n label: \"🔄 Пересоздать диаграммы\",\n variant: \"filled\",\n tint: \"primary\",\n onClick: async () => {\n await recreatePieCharts();\n }\n },\n {\n type: \"Button\", \n label: \"🗑️ Скрыть диаграммы\",\n variant: \"outlined\",\n onClick: async () => {\n await clearPieCharts();\n }\n }\n ]\n },\n {\n type: \"Button\",\n label: \"📊 Показать статистику\",\n onClick: async () => {\n await showStatistics();\n }\n },\n {\n type: \"Button\",\n label: \"🔍 Найти крупнейшие порты\",\n onClick: async () => {\n await findLargestPorts();\n }\n },\n {\n type: \"Button\",\n label: \"📍 Центрировать на Россию\",\n onClick: () => {\n felt.setViewport({\n center: { latitude: 64.0, longitude: 100.0 },\n zoom: 3\n });\n }\n }\n ],\n onClickClose: ({ id }) => {\n felt.deletePanel(id);\n }\n }\n });\n \n return panelId;\n}\n\n// Пересоздание диаграмм\nasync function recreatePieCharts() {\n await clearPieCharts();\n await createPieCharts();\n}\n\n// Очистка диаграмм\nasync function clearPieCharts() {\n console.log(\"Удаление существующих диаграмм...\");\n for (const element of pieChartElements) {\n try {\n await felt.deleteElement(element.id);\n } catch (error) {\n console.log(\"Ошибка при удалении элемента:\", error);\n }\n }\n pieChartElements = [];\n \n // Очищаем displayCoords\n for (const portName of Object.keys(portsData)) {\n delete portsData[portName].displayCoords;\n }\n}\n\n// Показать статистику\nasync function showStatistics() {\n const totalPorts = Object.keys(portsData).length;\n const totalCargo = Object.values(portsData).reduce((sum, port) => \n sum + Object.values(port.cargo).reduce((s, v) => s + v, 0), 0\n );\n \n const cargoStats = {};\n Object.values(portsData).forEach(port => {\n Object.entries(port.cargo).forEach(([type, amount]) => {\n cargoStats[type] = (cargoStats[type] || 0) + amount;\n });\n });\n \n const sortedCargo = Object.entries(cargoStats)\n .sort(([,a], [,b]) => b - a)\n .map(([type, total]) => `• ${cargoColors[type]?.emoji || '⚪'} ${type}: ${total.toFixed(1)} млн т (${((total/totalCargo)*100).toFixed(1)}%)`);\n \n const statsPanelId = await felt.createPanelId();\n await felt.createOrUpdatePanel({\n panel: {\n id: statsPanelId,\n title: \"📊 Статистика портов\",\n body: [\n {\n type: \"Text\",\n content: `**Общая статистика:**\\n\\n• Всего портов: ${totalPorts}\\n• Общий грузооборот: ${totalCargo.toFixed(1)} млн т\\n• Средний грузооборот: ${(totalCargo/totalPorts).toFixed(1)} млн т`\n },\n {\n type: \"Divider\"\n },\n {\n type: \"Text\",\n content: `**Грузооборот по типам:**\\n\\n${sortedCargo.join('\\n')}`\n },\n {\n type: \"Divider\"\n },\n {\n type: \"Text\",\n content: `**Цветовая схема диаграмм:**\\n\\n${Object.entries(cargoColors)\n .map(([type, data]) => `${data.emoji} ${type}`)\n .join('\\n')}`\n }\n ],\n onClickClose: ({ id }) => {\n felt.deletePanel(id);\n }\n }\n });\n}\n\n// Найти крупнейшие порты\nasync function findLargestPorts() {\n const portsBySize = Object.entries(portsData)\n .map(([name, data]) => ({\n name,\n total: Object.values(data.cargo).reduce((sum, val) => sum + val, 0),\n coords: data.displayCoords || data.coords, // Используем скорректированные координаты\n mainCargo: Object.entries(data.cargo).reduce((a, b) => a[1] > b[1] ? a : b)\n }))\n .sort((a, b) => b.total - a.total)\n .slice(0, 5);\n \n const highlights = [];\n for (const port of portsBySize) {\n try {\n const highlight = await felt.createElement({\n type: \"Circle\",\n center: port.coords,\n radius: 80000,\n name: `ТОП-порт: ${port.name}`,\n description: `Грузооборот: ${port.total.toFixed(1)} млн т`,\n color: \"#E74C3C\",\n fillOpacity: 0.1,\n strokeWidth: 4,\n strokeColor: \"#E74C3C\",\n strokeStyle: \"dashed\",\n interaction: \"locked\"\n });\n highlights.push(highlight);\n } catch (error) {\n console.log(`Ошибка создания выделения для ${port.name}:`, error);\n }\n }\n \n const topPortsPanelId = await felt.createPanelId();\n await felt.createOrUpdatePanel({\n panel: {\n id: topPortsPanelId,\n title: \"🏆 Крупнейшие порты России\",\n body: [\n {\n type: \"Text\",\n content: \"**ТОП-5 портов по грузообороту:**\\n\\n\" + \n portsBySize.map((port, index) => \n `**${index + 1}. ${port.name}**\\n${port.total.toFixed(1)} млн т\\nОсновной груз: ${port.mainCargo[0]} (${port.mainCargo[1]} млн т)`\n ).join('\\n\\n')\n },\n {\n type: \"Button\",\n label: \"Убрать выделение\",\n onClick: async () => {\n for (const highlight of highlights) {\n try {\n await felt.deleteElement(highlight.id);\n } catch (error) {\n console.log(\"Ошибка удаления выделения:\", error);\n }\n }\n felt.deletePanel(topPortsPanelId);\n }\n }\n ],\n onClickClose: async ({ id }) => {\n for (const highlight of highlights) {\n try {\n await felt.deleteElement(highlight.id);\n } catch (error) {\n console.log(\"Ошибка удаления выделения:\", error);\n }\n }\n felt.deletePanel(id);\n }\n }\n });\n \n felt.setViewport({\n center: { latitude: 64.0, longitude: 100.0 },\n zoom: 3\n });\n}\n\n// Обработчик кликов\nfelt.onPointerClick({\n handler: async (event) => {\n if (!event.features || event.features.length === 0) {\n return;\n }\n \n // Обрабатываем клики по элементам диаграммы\n const pieElements = event.features.filter(feature => \n feature.layerId && pieChartElements.some(el => \n el && el.id && feature.properties && \n feature.properties['felt:id'] === el.id\n )\n );\n \n if (pieElements.length > 0) {\n const feature = pieElements[0];\n const elementName = feature.properties.name || feature.properties['felt:name'];\n \n if (elementName && elementName.includes(':')) {\n const [portName, cargoType] = elementName.split(':').map(s => s.trim());\n const portData = portsData[portName];\n \n if (portData && cargoType && portData.cargo[cargoType]) {\n const amount = portData.cargo[cargoType];\n const totalCargo = Object.values(portData.cargo).reduce((sum, val) => sum + val, 0);\n const percentage = (amount / totalCargo * 100).toFixed(1);\n const emoji = cargoColors[cargoType]?.emoji || '⚪';\n \n const segmentInfoPanelId = await felt.createPanelId();\n await felt.createOrUpdatePanel({\n panel: {\n id: segmentInfoPanelId,\n title: `${emoji} ${cargoType} - ${portName}`,\n body: [\n {\n type: \"Text\",\n content: `**Объем груза:** ${amount} млн т\\n**Доля в порту:** ${percentage}%\\n**Общий грузооборот порта:** ${totalCargo.toFixed(1)} млн т`\n },\n {\n type: \"Button\",\n label: \"📍 Показать на карте\",\n onClick: () => {\n const coords = portData.displayCoords || portData.coords;\n felt.setViewport({\n center: { latitude: coords[1], longitude: coords[0] },\n zoom: 12\n });\n }\n }\n ],\n onClickClose: ({ id }) => {\n felt.deletePanel(id);\n }\n }\n });\n }\n }\n return;\n }\n \n // Обрабатываем клики по базовому слою портов\n const portFeatures = event.features.filter(feature => \n feature.layerId && feature.properties && feature.properties.name && portsData[feature.properties.name]\n );\n \n if (portFeatures.length > 0) {\n const feature = portFeatures[0];\n const portName = feature.properties.name;\n const portData = portsData[portName];\n \n if (portData) {\n const totalCargo = Object.values(portData.cargo).reduce((sum, val) => sum + val, 0);\n const cargoBreakdown = Object.entries(portData.cargo)\n .sort(([,a], [,b]) => b - a)\n .map(([type, amount]) => {\n const emoji = cargoColors[type]?.emoji || '⚪';\n return `• ${emoji} ${type}: ${amount} млн т (${((amount/totalCargo)*100).toFixed(1)}%)`;\n });\n \n const portInfoPanelId = await felt.createPanelId();\n await felt.createOrUpdatePanel({\n panel: {\n id: portInfoPanelId,\n title: `🚢 ${portName}`,\n body: [\n {\n type: \"Text\",\n content: `**Общий грузооборот:** ${totalCargo.toFixed(1)} млн т\\n\\n**Структура грузооборота:**\\n\\n${cargoBreakdown.join('\\n')}`\n },\n {\n type: \"Button\",\n label: \"📍 Приблизить к порту\",\n onClick: () => {\n const coords = portData.displayCoords || portData.coords;\n felt.setViewport({\n center: { latitude: coords[1], longitude: coords[0] },\n zoom: 12\n });\n }\n }\n ],\n onClickClose: ({ id }) => {\n felt.deletePanel(id);\n }\n }\n });\n }\n }\n }\n});\n\n// Инициализация\nasync function initialize() {\n console.log(\"Инициализация карты портов России...\");\n \n try {\n // Создаем базовый слой\n console.log(\"Создание базового слоя...\");\n const layerResult = await createPortsLayer();\n console.log(\"Базовый слой создан:\", layerResult ? \"успешно\" : \"ошибка\");\n \n // Создаем круговые диаграммы с разрешением конфликтов\n console.log(\"Создание круговых диаграмм с оптимизацией позиций...\");\n await createPieCharts();\n \n // Создаем панель управления\n console.log(\"Создание панели управления...\");\n await createControlPanel();\n \n // Центрируем карту на России\n felt.setViewport({\n center: { latitude: 64.0, longitude: 100.0 },\n zoom: 3\n });\n \n console.log(\"✅ Карта портов с неперекрывающимися диаграммами готова!\");\n console.log(\"💡 Кликните на сегмент диаграммы для детальной информации\");\n console.log(\"📏 Смещенные диаграммы соединены пунктирными линиями с исходными позициями портов\");\n \n } catch (error) {\n console.error(\"❌ Ошибка при инициализации:\", error);\n }\n}\n\n// Запуск\ninitialize();","insertedAt":"2025-10-27T19:28:32","zOrder":-1}]}