From a72304557e08bc0e60496902e83913f6a6de04dd Mon Sep 17 00:00:00 2001 From: Glauber Costa Vila-Verde Date: Tue, 22 Dec 2020 14:29:10 -0300 Subject: [PATCH] Feat/v2.3 (#1307) * Sky Viewer UI improvements (#1273) * Changed the position of the 'Map viewer' window now it is displayed centered on the screen following the pattern of the other windows. this change was to prevent the user from confusing it with the other menu items. #1260 * Region Overlay and Snapshot functions have been disabled in Visiomatic #1260 * The visiomatic toolbar where the zoom controls are located has been changed so that it has a better contrast. #1260 * Target Viwer UI improvements (#1274) * The list of target viewer catalogs is now ordered by the most recent date by default. #1260 * The toolbar of the list of objects in the target viewer has been reorganized, now the buttons follow the natural order of execution. #1260 * The Contact Us entry in the application menu has been removed. #1260 * The order of the buttons in the list of targets has been changed. #1260 * Feat/landing page (#1277) * Update Django to v2.2.17 (#1256) * Update Django and other related libs. Django 2.1.15 -> 2.2.17 #1255 * Update of other dependencies * Integrate with DESaccess (#1258) * Minor correction in the target catalogs query using postgresql * Start of integration with Descut service. * Created methods to Submit and Check the status of the Descut job. and the creation of the method to download the files has started. * Created methods to download the results, match between targets and images. the method of registering images is still incomplete. * Finalized the methods of registering images. Correction of target queries. creation of methods that retrieve the list of objects before submitting. * Partial implementation of the cutout workflow. * Integration with DESaccess cutout has been implemented. workflow created for submitting and downloading cutout jobs. #1257 * Created methods that allow integration with DESaccess Tile Finder. docstrings of the methods related to Cutout have been added. #1257 * Commenting the 'teste' endpoint Co-authored-by: Matheus Allein * Target Cutout and Integration with DESaccess (#1267) * Minor correction in the target catalogs query using postgresql * Start of integration with Descut service. * Created methods to Submit and Check the status of the Descut job. and the creation of the method to download the files has started. * Created methods to download the results, match between targets and images. the method of registering images is still incomplete. * Finalized the methods of registering images. Correction of target queries. creation of methods that retrieve the list of objects before submitting. * Partial implementation of the cutout workflow. * Integration with DESaccess cutout has been implemented. workflow created for submitting and downloading cutout jobs. #1257 * Created methods that allow integration with DESaccess Tile Finder. docstrings of the methods related to Cutout have been added. #1257 * Cutout Jobs submission form has been changed to be compatible with the new version of DESaccess. new color image options have been added. #1257 * Alteration of the Mosaic panel has started to allow the selection of more than one type of color image. Cutout Jobs loading and selection has been improved, now the job list will always be updated with all available jobs and the most recent job will be selected by default. #1257 * Refactoring the visualization of the cutouts, it is now possible to choose the color image used in the mosaic. * minor fix in default settings #1257 * The discard_fits_files function has been implemented to avoid downloading fits files that have not been requested by the user. #1257 * Download Target List compatible with DESacces. (#1269) * Minor fix in cutout. * A correction was made to the download feature. to be compatible with the new version of DESaccess. * Change labels Mosaic -> Cutout * Minor fix in cutout (#1270) * Fixing contrast bug and removing features from tile inspection (#1259) * Fixing contrast bug and removing features from tile inspection * Changing virtual list library * Fixing reload logic based on new virtual list * Fixing release change not loading datasets * The name of the configuration variable has been changed. * Enabling clear search and positioning visiomatic on search by position Co-authored-by: glaubervila * Feat/eyeballing tile download (#1268) * Fetching tile info and displaying on download dialog * Fetching tile info and displaying on download dialog * Fetching tile info and displaying on download dialog * Adding error handler for the case of not having data on tile * Fixing release change not loading datasets * feat: home page adjustments. * feat: Link Sing Up. #1260 * An endpoint was created that returns the url to the NCSA registration page. * feat: Sing Up Api. #1260 Co-authored-by: Glauber Costa Vila-Verde Co-authored-by: Matheus Allein * Fixed #1280 error when ordering a list of targets. error was introduced after the sqlalchemy update, now every time an aliased field is used in the ordering the text () function must be used. (#1281) * Fixed error in target comments and other minor fixes. (#1284) * Fixed #1282 error when inserting a comment in the list of objects when using oracle database. * The comment function by position has been disabled. Another color option was included in the cutout. A bug was fixed in the Target preview that prevented the creation of a new comment. * The part of the menu that listed the External Catalogs tables was removed from the user query code. for the image viewer, only the registration of the tables was removed. #1287 (#1289) * Fixed an error that prevented when executing an overlay of catalogs, the condition that determines whether the filter by positions will be used was in problem. #1285 (#1286) * The Vizier Gaia DR3 catalog has been added to the list of external catalogs. #1290 (#1291) * Adding favicon to applications (#1295) * Feat/tile viewer validation improvements (#1293) * Changing routes /eyeballing to /tile_viewer * Standard toolbar; changing tiles counter * Tile list navigation by arrow keys * Adding the contrast button on Visiomatic * Removing the copy to clipboard tile position * Enable coordinates by degrees option * Fixing small bugs * Adding no search results feedback and including hhmmss search * Changing toolbar and footer color * Fixed bugs after validation * Fixed bugs after validation * Feat/landing page (#1296) * feat: Link Sing Up. #1260 * fix: changing method names. #1260 * feat: instructions for login or sign up. #1260 * fix: adjustment in code writing. #1260 * Only the first tile was loading properly (#1298) * Registration of public releases. (#1299) * Fixed ellipses orientation for DR2. * Added examples of coadd table records. * The functions for recording public releases DR1 and DR2 have been included in the datadiscovery. * Closed #1297 * Changed the color options for the cutouts, and fixed a bug that prevented the exchange between images. (#1300) * feat: google analytics implementations. #1275 (#1276) * feat: google analytics implementations. #1275 * Closed #1275 The Google Analytics upload script has been changed. * missing gitignore Co-authored-by: glaubervila * Fixing search by position loading forever (#1301) * Fix/user query check (#1302) * Removing button "Check" from User Query * Adding traceback to User Query error * minor fix in google analytics load script (#1303) * Fix/analytics (#1304) * minor fix in google analytics load script * Fix titles first analytics * User query external catalog tables. (#1306) * The External tables menu was enabled in the userquery but without any content. #1305 * Minor change in the target viewer, the download fits image button has been disabled while the new functionality is not ready. Co-authored-by: Fernando Nicolau Co-authored-by: Matheus Allein --- .gitignore | 2 + Docs/exemplo_importacao_coadd_object_dr1.json | 65 + Docs/exemplo_importacao_coadd_object_dr2.json | 65 + .../exemplo_importacao_coadd_object_y6a1.json | 65 + .../exemplo_importacao_coadd_object_y6a2.json | 2 +- api/catalog/serializers.py | 6 +- api/catalog/views.py | 50 +- api/coadd/datadiscovery.py | 71 +- .../management/commands/datadiscovery.py | 7 + api/lib/CatalogDB.py | 7 +- api/product/views.py | 2 - api/product/viziercds.py | 21 + api/userquery/views.py | 3 + docker-compose-production.yml | 3 +- frontend/Dockerfile | 2 + frontend/explorer/favicon.png | Bin 0 -> 20745 bytes frontend/explorer/index.html | 20 +- frontend/eyeballing/.env.production | 4 +- frontend/eyeballing/public/favicon.ico | Bin 3870 -> 0 bytes frontend/eyeballing/public/favicon.png | Bin 0 -> 20745 bytes frontend/eyeballing/public/index.html | 19 +- .../visiomatic/dist/visiomatic-src.js | 12584 +++++++--------- .../visiomatic/visiomatic/dist/visiomatic.css | 22 +- .../visiomatic/visiomatic/src/Catalog.js | 101 +- frontend/eyeballing/src/api/Api.js | 2 +- .../src/components/ChooseFilterDialog.js | 108 +- frontend/eyeballing/src/components/Counter.js | 63 +- frontend/eyeballing/src/components/Header.js | 55 +- .../src/components/TutorialDialog.js | 8 +- .../src/components/visiomatic/ContrastMenu.js | 78 + .../src/components/visiomatic/Visiomatic.js | 152 +- frontend/eyeballing/src/home.js | 379 +- frontend/eyeballing/src/index.css | 4 + .../eyeballing/src/theme/MaterialTheme.js | 2 +- frontend/ga/google-analytics.js | 26 + frontend/landing_page/public/index.html | 18 +- frontend/landing_page/src/Routes/index.js | 2 + frontend/landing_page/src/Services/api.js | 6 +- .../src/components/Header/index.js | 12 +- .../src/pages/OracleEasyAccess/index.js | 111 + .../src/pages/OracleEasyAccess/styles.js | 45 + frontend/nginx-proxy-develop.conf | 9 + frontend/nginx-proxy.conf | 14 +- .../local/common/src/model/CommentObject.js | 14 +- .../local/visiomatic/src/Visiomatic.js | 4 +- frontend/products/favicon.png | Bin 0 -> 20745 bytes frontend/products/index.html | 12 +- frontend/sky/app/view/home/Home.js | 106 +- frontend/sky/favicon.png | Bin 0 -> 20745 bytes frontend/sky/index.html | 19 +- .../app/view/objects/ObjectsController.js | 4 +- frontend/target/app/view/preview/Preview.js | 98 +- .../app/view/preview/PreviewController.js | 174 +- .../target/app/view/settings/CutoutJobForm.js | 14 +- frontend/target/favicon.png | Bin 0 -> 20745 bytes frontend/target/index.html | 20 +- frontend/userquery/app/view/main/Main.js | 47 +- .../userquery/app/view/main/MainController.js | 201 +- frontend/userquery/favicon.png | Bin 0 -> 20745 bytes frontend/userquery/index.html | 17 +- 60 files changed, 7393 insertions(+), 7552 deletions(-) create mode 100644 Docs/exemplo_importacao_coadd_object_dr1.json create mode 100644 Docs/exemplo_importacao_coadd_object_dr2.json create mode 100644 Docs/exemplo_importacao_coadd_object_y6a1.json create mode 100644 frontend/explorer/favicon.png delete mode 100755 frontend/eyeballing/public/favicon.ico create mode 100644 frontend/eyeballing/public/favicon.png create mode 100644 frontend/eyeballing/src/components/visiomatic/ContrastMenu.js create mode 100644 frontend/ga/google-analytics.js create mode 100644 frontend/landing_page/src/pages/OracleEasyAccess/index.js create mode 100644 frontend/landing_page/src/pages/OracleEasyAccess/styles.js create mode 100644 frontend/products/favicon.png create mode 100644 frontend/sky/favicon.png create mode 100644 frontend/target/favicon.png create mode 100644 frontend/userquery/favicon.png diff --git a/.gitignore b/.gitignore index 3602c407e..4932968d1 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,5 @@ __pycache__ /pg_data /pg_backup +# Arquivo do google-analytics +google-analytics.js diff --git a/Docs/exemplo_importacao_coadd_object_dr1.json b/Docs/exemplo_importacao_coadd_object_dr1.json new file mode 100644 index 000000000..5c10eaa53 --- /dev/null +++ b/Docs/exemplo_importacao_coadd_object_dr1.json @@ -0,0 +1,65 @@ +{ + "products": [ + { + "display_name": "DR1", + "fields": [], + "releases": [ + "dr1" + ], + "table": "DR1_MAIN", + "schema": "DES_ADMIN", + "database": "desdr", + "type": "catalog", + "class": "coadd_objects", + "name": "dr1_coadd", + "association": [ + { + "ucd": "meta.id;meta.main", + "property": "coadd_object_id" + }, + { + "ucd": "pos.eq.ra;meta.main", + "property": "ra" + }, + { + "ucd": "pos.eq.dec;meta.main", + "property": "dec" + }, + { + "ucd": "phys.size.smajAxis;instr.det;meta.main", + "property": "a_image" + }, + { + "ucd": "phys.size.sminAxis;instr.det;meta.main", + "property": "b_image" + }, + { + "ucd": "pos.posAng;instr.det;meta.main", + "property": "theta_j2000" + }, + { + "ucd": "phot.mag;meta.main;em.opt.g", + "property": "mag_auto_g" + }, + { + "ucd": "phot.mag;meta.main;em.opt.r", + "property": "mag_auto_r" + }, + { + "ucd": "phot.mag;meta.main;em.opt.i", + "property": "mag_auto_i" + }, + { + "ucd": "phot.mag;meta.main;em.opt.z", + "property": "mag_auto_z" + }, + { + "ucd": "phot.mag;meta.main;em.opt.Y", + "property": "mag_auto_y" + } + ] + } + ], + "ticket": "GZY650", + "register_username": "gverde" +} \ No newline at end of file diff --git a/Docs/exemplo_importacao_coadd_object_dr2.json b/Docs/exemplo_importacao_coadd_object_dr2.json new file mode 100644 index 000000000..706b54b01 --- /dev/null +++ b/Docs/exemplo_importacao_coadd_object_dr2.json @@ -0,0 +1,65 @@ +{ + "products": [ + { + "display_name": "DR2", + "fields": [], + "releases": [ + "dr2" + ], + "table": "DR2_MAIN_RC", + "schema": "DES_ADMIN", + "database": "dessci", + "type": "catalog", + "class": "coadd_objects", + "name": "dr2", + "association": [ + { + "ucd": "meta.id;meta.main", + "property": "coadd_object_id" + }, + { + "ucd": "pos.eq.ra;meta.main", + "property": "ra" + }, + { + "ucd": "pos.eq.dec;meta.main", + "property": "dec" + }, + { + "ucd": "phys.size.smajAxis;instr.det;meta.main", + "property": "a_image" + }, + { + "ucd": "phys.size.sminAxis;instr.det;meta.main", + "property": "b_image" + }, + { + "ucd": "pos.posAng;instr.det;meta.main", + "property": "theta_j2000" + }, + { + "ucd": "phot.mag;meta.main;em.opt.g", + "property": "mag_auto_g" + }, + { + "ucd": "phot.mag;meta.main;em.opt.r", + "property": "mag_auto_r" + }, + { + "ucd": "phot.mag;meta.main;em.opt.i", + "property": "mag_auto_i" + }, + { + "ucd": "phot.mag;meta.main;em.opt.z", + "property": "mag_auto_z" + }, + { + "ucd": "phot.mag;meta.main;em.opt.Y", + "property": "mag_auto_y" + } + ] + } + ], + "ticket": "VDB377", + "register_username": "gverde" +} \ No newline at end of file diff --git a/Docs/exemplo_importacao_coadd_object_y6a1.json b/Docs/exemplo_importacao_coadd_object_y6a1.json new file mode 100644 index 000000000..feefe9a9f --- /dev/null +++ b/Docs/exemplo_importacao_coadd_object_y6a1.json @@ -0,0 +1,65 @@ +{ + "products": [ + { + "display_name": "Y6A1 Coadd", + "fields": [], + "releases": [ + "y6a1_coadd" + ], + "table": "Y6A1_COADD_OBJECT_SUMMARY", + "schema": "DES_ADMIN", + "database": "dessci", + "type": "catalog", + "class": "coadd_objects", + "name": "y6a1_coadd", + "association": [ + { + "ucd": "meta.id;meta.main", + "property": "coadd_object_id" + }, + { + "ucd": "pos.eq.ra;meta.main", + "property": "ra" + }, + { + "ucd": "pos.eq.dec;meta.main", + "property": "dec" + }, + { + "ucd": "phys.size.smajAxis;instr.det;meta.main", + "property": "a_image" + }, + { + "ucd": "phys.size.sminAxis;instr.det;meta.main", + "property": "b_image" + }, + { + "ucd": "pos.posAng;instr.det;meta.main", + "property": "theta_j2000" + }, + { + "ucd": "phot.mag;meta.main;em.opt.g", + "property": "mag_auto_g" + }, + { + "ucd": "phot.mag;meta.main;em.opt.r", + "property": "mag_auto_r" + }, + { + "ucd": "phot.mag;meta.main;em.opt.i", + "property": "mag_auto_i" + }, + { + "ucd": "phot.mag;meta.main;em.opt.z", + "property": "mag_auto_z" + }, + { + "ucd": "phot.mag;meta.main;em.opt.Y", + "property": "mag_auto_y" + } + ] + } + ], + "ticket": "GZY650", + "register_username": "gverde" +} \ No newline at end of file diff --git a/Docs/exemplo_importacao_coadd_object_y6a2.json b/Docs/exemplo_importacao_coadd_object_y6a2.json index 35d1694de..70f5247bb 100644 --- a/Docs/exemplo_importacao_coadd_object_y6a2.json +++ b/Docs/exemplo_importacao_coadd_object_y6a2.json @@ -60,6 +60,6 @@ ] } ], - "ticket": "YKP478", + "ticket": "GZY650", "register_username": "gverde" } \ No newline at end of file diff --git a/api/catalog/serializers.py b/api/catalog/serializers.py index 12a44405a..80ac4fea2 100644 --- a/api/catalog/serializers.py +++ b/api/catalog/serializers.py @@ -39,9 +39,9 @@ class Meta: class CommentsSerializer(serializers.HyperlinkedModelSerializer): catalog_id = IntegerField(allow_null=False) object_id = CharField(min_length=1, max_length=255, allow_null=False, required=True) - owner = serializers.SerializerMethodField() - date = serializers.SerializerMethodField() - is_owner = serializers.SerializerMethodField() + owner = serializers.SerializerMethodField(read_only=True) + date = serializers.SerializerMethodField(read_only=True) + is_owner = serializers.SerializerMethodField(read_only=True) class Meta: model = Comments diff --git a/api/catalog/views.py b/api/catalog/views.py index 2cd00e687..dc998840f 100644 --- a/api/catalog/views.py +++ b/api/catalog/views.py @@ -1,16 +1,16 @@ +import math + from django.conf import settings -from lib.CatalogDB import TargetObjectsDBHelper, CatalogObjectsDBHelper +from django.db.models import Max +from lib.CatalogDB import CatalogObjectsDBHelper, TargetObjectsDBHelper, CatalogDB from product.association import Association -from product.models import Catalog, ProductContentAssociation -from product.serializers import AssociationSerializer +from product.models import Catalog from rest_framework import viewsets from rest_framework.response import Response from rest_framework.viewsets import ViewSet -from .models import Rating, Reject, Comments -from .serializers import RatingSerializer, RejectSerializer, CommentsSerializer - -import math +from .models import Comments, Rating, Reject +from .serializers import CommentsSerializer, RatingSerializer, RejectSerializer class RatingViewSet(viewsets.ModelViewSet): @@ -62,7 +62,39 @@ class CommentsViewSet(viewsets.ModelViewSet): def perform_create(self, serializer): if not self.request.user.pk: raise Exception('It is necessary an active login to perform this operation.') - serializer.save(owner=self.request.user.pk) + + # TODO: Rever essa solução tentar deixar igual ao comportamento do rating e reject. + # Workaround para corrigir bug reportado na issue https://github.com/linea-it/dri/issues/1282 + # Por Algum motivo que eu não consegui descobrir, ocorre um erro + # ao tenta inserir um registro na tabela comments usando Oracle. + # o Oracle reclama que ID não pode ser Null, mas a coluna id é autoincrement, + # nas tabelas Rating e Reject é a mesma situação e nelas o erro não acontece. + # A solução: + # Verificar se o banco utilizado é o Oracle. + # se for faz uma query para na tabela e depois recupera o ultimo Id + # acrescenta +1 e faz o insert usando este ID. + + # Problemas: + # - Faz uma query all antes do insert, pode causar um problema de performance se a tabela commens ficar muito grande. + # - Pode ocorrer falhar se 2 usuarios fizerem um comment ao mesmo tempo. + + # Verificar se a tabela comments está no Oracle + catalog_db = CatalogDB(db='catalog') + if catalog_db.get_engine() == "oracle": + # Proximo ID + comments = Comments.objects.all() + next_id = comments.aggregate(Max('id'))['id__max'] + 1 if comments else 1 + + # Faz o Insert + serializer.save( + owner=self.request.user.pk, + pk=next_id + ) + else: + # Se não for oracle deixa o Django fazer o processo normal de insert. + serializer.save( + owner=self.request.user.pk, + ) class TargetViewSet(ViewSet): @@ -345,7 +377,7 @@ def list(self, request): if release_set: release = release_set.release.rls_name # Se tiver release e ele for o Y3 subtrair 90 graus - areleases = ['y3a1_coadd', 'y6a1_coadd', 'y6a2_coadd', 'dr1', ] + areleases = ['y3a1_coadd', 'y6a1_coadd', 'y6a2_coadd', 'dr1', 'dr2'] if release in areleases: t_image = 90 - t_image diff --git a/api/coadd/datadiscovery.py b/api/coadd/datadiscovery.py index 3f8e2f221..5883776d6 100644 --- a/api/coadd/datadiscovery.py +++ b/api/coadd/datadiscovery.py @@ -1,8 +1,10 @@ +from pprint import pprint + import cx_Oracle -from coadd.models import Release, Dataset, Tile, Tag from django.conf import settings -from pprint import pprint +import django.utils.timezone as tz +from coadd.models import Dataset, Release, Tag, Tile # query and database change according with the release to register DATADISCOVERY = { @@ -41,15 +43,38 @@ 'TILES_COUNT': 'SELECT COUNT(*) AS count FROM proctag t WHERE t.tag=\'Y6A2_COADD\'', 'PTIF_PATHS': 'SELECT m.tilename, f.PATH AS archive_path, m.filename, t.created_date FROM proctag t LEFT JOIN miscfile m ON (t.pfw_attempt_id = m.pfw_attempt_id) LEFT JOIN file_archive_info f ON (f.filename = m.filename) WHERE t.tag=\'Y6A2_COADD\' AND m.filetype=\'coadd_ptif\'' } - } + }, + # Release publico, este release é identico ao Y3A2 nas imagens e tiles. + 'DR1': { + 'DATABASE': 'dessci', + # Este parametro Indica que o release é uma copia de outro release, vai manter o nome mas vai registrar os dados do outro. + 'FAKE_RELEASE': True, + 'QUERIES': { + 'TAG': 'SELECT t.tag, MIN(t.created_date) FROM des_admin.y3a2_proctag t WHERE t.tag=\'Y3A2_COADD\' GROUP BY t.tag', + 'TILES_COUNT': 'SELECT COUNT(*) AS count FROM y3a2_proctag t WHERE t.tag=\'Y3A2_COADD\'', + 'PTIF_PATHS': 'SELECT m.tilename, f.path AS archive_path, m.filename, t.created_date FROM y3a2_proctag t, y3a2_file_archive_info f, y3a2_miscfile m WHERE t.pfw_attempt_id = m.pfw_attempt_id AND t.tag=\'Y3A2_COADD\' AND f.filename=m.filename AND m.filetype=\'coadd_ptif\'' + } + }, + # Release publico, este release é identico ao Y6A2 nas imagens e tiles. + 'DR2': { + 'DATABASE': 'desoper', + # Este parametro Indica que o release é uma copia de outro release, vai manter o nome mas vai registrar os dados do outro. + 'FAKE_RELEASE': True, + 'QUERIES': { + 'TAG': 'SELECT t.tag, MIN(t.created_date) as created_date FROM proctag t WHERE t.tag=\'Y6A2_COADD\' GROUP BY t.tag', + 'TILES_COUNT': 'SELECT COUNT(*) AS count FROM proctag t WHERE t.tag=\'Y6A2_COADD\'', + 'PTIF_PATHS': 'SELECT m.tilename, f.PATH AS archive_path, m.filename, t.created_date FROM proctag t LEFT JOIN miscfile m ON (t.pfw_attempt_id = m.pfw_attempt_id) LEFT JOIN file_archive_info f ON (f.filename = m.filename) WHERE t.tag=\'Y6A2_COADD\' AND m.filetype=\'coadd_ptif\'' + } + }, } } - class DataDiscovery: def __init__(self, options): + self.image_host = options['image_host'] + self.tag = options['tag'] self.tag_config = DATADISCOVERY.get('RELEASES').get(self.tag) @@ -66,7 +91,7 @@ def __init__(self, options): try: pprint(dbparams) - print ("Connecting to database") + print("Connecting to database") host, port = dbparams.get('NAME').split(':') port, servicename = port.split('/') @@ -80,7 +105,7 @@ def __init__(self, options): self.db = cx_Oracle.connect(dbparams.get('USER'), dbparams.get('PASSWORD'), dsn=dsn) self.cursor = self.db.cursor() - print ("Connected") + print("Connected") except Exception as e: print(e) @@ -98,15 +123,15 @@ def start(self): "tag='Y3A1_COADD'", "tag='Y3A2_COADD'", "tag='Y3A1_COADD_DEEP'"] for pattern in patterns: - print ("--------------------------------------") + print("--------------------------------------") sql = "SELECT tag, MIN(created_date) as created_date FROM PROD.PROCTAG WHERE %s GROUP BY tag ORDER BY created_date" % pattern - print ("Finding Tags available: [ %s ]" % sql) + print("Finding Tags available: [ %s ]" % sql) rows = self.fetchall_dict(sql) - print ("Tags available: [ %s ]" % len(rows)) + print("Tags available: [ %s ]" % len(rows)) for row in rows: print("Tag Name: %s" % row.get('TAG')) @@ -122,7 +147,7 @@ def start(self): else: self.register_tag(self.tag) - print ("Done!") + print("Done!") def register_tag(self, requested_tag): @@ -139,13 +164,22 @@ def register_tag(self, requested_tag): row = rows[0] tag = row.get('TAG') + real_tag = tag + created_date = row.get('CREATED_DATE') print("Tag: %s Created Date: %s" % (tag, row.get('CREATED_DATE'))) + if self.tag_config.get('FAKE_RELEASE', False): + tag = self.tag + print("This is a release based on another, the internal name will be %s but its content will be %s" % (self.tag, real_tag)) + + if created_date is None: + created_date = tz.localtime() + # Checar se o Release ja existe no DRI se nao existir criar rls_display_name = self.generate_display_name(tag) rls_name = tag.lower() - rls_date = row.get('CREATED_DATE') + rls_date = created_date rls_version = 1.0 release, created = Release.objects.select_related().get_or_create( @@ -175,7 +209,7 @@ def register_tag(self, requested_tag): print("Field Created?: [ %s ] Name: [ %s ] ID: [ %s ] " % (created, field, field.id)) - tiles = self.get_tiles_by_tag(tag, field) + tiles = self.get_tiles_by_tag(real_tag, field) count_created = 0 count_updated = 0 @@ -235,7 +269,7 @@ def get_tiles_by_tag(self, tag, field): print("Tiles Installed [ %s ] Recent Date [ %s ]" % (dri_count, last_date)) if original_count != dri_count: - print ('Tiles to be installed [ %s ]' % (original_count - dri_count)) + print('Tiles to be installed [ %s ]' % (original_count - dri_count)) sql = self.tag_config.get('QUERIES').get('PTIF_PATHS') @@ -249,8 +283,12 @@ def get_tiles_by_tag(self, tag, field): tiles = self.fetchall_dict(sql) for tile in tiles: - image_src_ptif = "https://desportal.cosmology.illinois.edu/visiomatic?FIF=data/releases/desarchive/%s/%s" % ( - tile.get('ARCHIVE_PATH'), tile.get('FILENAME')) + # src = image_host + visiomatic url and path for images in DES. + # Exemple: https://desportal.cosmology.illinois.edu/visiomatic?FIF=data/releases/desarchive/%s/%s + # TODO: Melhorar o path para ser independente da estrutura do DES. agora com container os releases + # poderiam ser montados direto em /data/releases/y3a1 por exemplo. + image_src_ptif = "%s/visiomatic?FIF=data/releases/desarchive/%s/%s" % (self.image_host, + tile.get('ARCHIVE_PATH'), tile.get('FILENAME')) tile.update({ 'image_src_ptif': image_src_ptif.replace("+", "%2B") @@ -298,7 +336,6 @@ def fetch_scalar(self, query, col=0): else: return None - # def assocition_tag_tile(self): # """ # Usadado para importar um csv com as tiles que fazem parte do release Y1A1 essa funcao faz a assiciacao da @@ -341,7 +378,7 @@ def fetch_scalar(self, query, col=0): if __name__ == '__main__': - print ("---------- stand alone -------------") + print("---------- stand alone -------------") DataDiscovery().start() diff --git a/api/coadd/management/commands/datadiscovery.py b/api/coadd/management/commands/datadiscovery.py index 29516ccb9..7849d8e5b 100644 --- a/api/coadd/management/commands/datadiscovery.py +++ b/api/coadd/management/commands/datadiscovery.py @@ -15,5 +15,12 @@ def add_arguments(self, parser): help='Name of the release corresponding to the name the DES uses.', ) + parser.add_argument( + '--image_host', + dest='image_host', + default='https://desportal.cosmology.illinois.edu', + help='Hostname where the iipserver server is. to devs leave the default value to use production images. for production environments inform the domain where the application is installed. with protocol and without / at the end. example: https://desportal.cosmology.illinois.edu', + ) + def handle(self, *args, **options): DataDiscovery(options).start() diff --git a/api/lib/CatalogDB.py b/api/lib/CatalogDB.py index 0547efc3f..50d3fcb08 100644 --- a/api/lib/CatalogDB.py +++ b/api/lib/CatalogDB.py @@ -7,7 +7,7 @@ from sqlalchemy import Column, cast, desc from sqlalchemy import exc as sa_exc from sqlalchemy import func -from sqlalchemy.sql import and_, or_, select +from sqlalchemy.sql import and_, or_, select, text from sqlalchemy.sql.expression import between, literal_column from lib.sqlalchemy_wrapper import DBBase @@ -369,7 +369,7 @@ def create_stm(self, columns=list(), filters=None, ordering=None, limit=None, st base_filters = and_(*self.do_filter(self.table, filters)) - if coordinates_filter: + if coordinates_filter is not None: stm = stm.where(and_(base_filters, coordinates_filter)) else: stm = stm.where(base_filters) @@ -595,7 +595,6 @@ def create_stm(self, columns=list(), filters=None, ordering=None, limit=None, st base_filters = and_(*self.do_filter(self.table, filters)) stm = stm.where(and_(base_filters, coordinate_filters, rating_filters, reject_filters)) - # stm = stm.where(and_(rating_filters, reject_filters)) # Ordenacao if self.ordering is not None: @@ -611,7 +610,7 @@ def create_stm(self, columns=list(), filters=None, ordering=None, limit=None, st elif property == '_meta_reject': property = catalog_reject.c.reject else: - property = 'a.' + property + property = text('a.' + property) if asc: stm = stm.order_by(property) diff --git a/api/product/views.py b/api/product/views.py index a3cedd6de..dcbbebc55 100644 --- a/api/product/views.py +++ b/api/product/views.py @@ -281,8 +281,6 @@ def get_class_tree_by_group(self, request): # Se ja tiver o grupo External Catalog adiciona os catalogos do vizier como children if group_name == 'external_catalogs' and external_catalogs_vizier is not None: - from pprint import pprint - # pprint(external_catalogs_vizier.get('children')[0].get('children')) nodeg['children'].append(external_catalogs_vizier.get('children')[0]) result.get('children').append(nodeg) diff --git a/api/product/viziercds.py b/api/product/viziercds.py index f08b4874c..82f688e1c 100644 --- a/api/product/viziercds.py +++ b/api/product/viziercds.py @@ -262,6 +262,27 @@ def get_available_catalogs(self): "iconCls": "no-icon", "leaf": True, }), + # GAIA_DR3 + dict({ + "id": "vizier_gaia_dr3", + "external_catalog": True, + "prd_name": "visier_gaia_dr3", + "prd_display_name": "GAIA EDR3", + "cds_source": "I/350", + "cds_fieldnames": ",".join(["Source", "RA_ICRS", "DE_ICRS", "Plx", "e_Plx", "pmRA", "e_pmRA", "pmDE", "e_pmDE", "Gmag", "e_Gmag", "BPmag", "e_BPmag", "RPmag", "e_RPmag", "RVDR2", "e_RVDR2"]), + "ctl_num_objects": 0, + "description": "Gaia data early release 3 (Gaia EDR3, 2020)", + "owner": "Vizier", + "pcl_is_system": False, + "is_owner": False, + "text": "GAIA EDR3", + "bookmark": None, + "tableExist": True, + "editable": False, + "markable": False, + "iconCls": "no-icon", + "leaf": True, + }), # URAT1 dict({ "id": "vizier_urat1", diff --git a/api/userquery/views.py b/api/userquery/views.py index 6828027de..434e083b4 100644 --- a/api/userquery/views.py +++ b/api/userquery/views.py @@ -1,5 +1,6 @@ import copy import logging +import traceback from django.contrib.auth.models import User from django.db.models import Case, F, Q, Value, When @@ -269,6 +270,8 @@ def create(self, request): return JsonResponse(response, safe=False) except Exception as e: + trace = traceback.format_exc() + print(trace) print(str(e)) return JsonResponse({'message': str(e)}, status=400) diff --git a/docker-compose-production.yml b/docker-compose-production.yml index a7e809ce6..2e7642941 100644 --- a/docker-compose-production.yml +++ b/docker-compose-production.yml @@ -57,7 +57,8 @@ services: - ./archive/django_static:/var/www/django_static # Diretório de dados da aplicação - ./archive:/var/www/data - + # Google Analytics Script + - ./google-analytics.js:/var/www/ga/google-analytics.js depends_on: - backend - iipserver diff --git a/frontend/Dockerfile b/frontend/Dockerfile index b7ce0eaae..937f4d5c0 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -55,3 +55,5 @@ COPY --from=eyeballing /app/build /var/www/eyeballing # Add Landing Page React App to image COPY --from=landingpage /app/build /var/www/landing_page +# Add Directory where google analytics scripts are located +COPY ./ga /var/www/ga diff --git a/frontend/explorer/favicon.png b/frontend/explorer/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..d5a5331be222f0e0e65629205497fd545e48ae07 GIT binary patch literal 20745 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 RQ!I} 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�G-*Rhu9Lb{Hx6Ms0u6>M^(G~Q( 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 literal 0 HcmV?d00001 diff --git a/frontend/explorer/index.html b/frontend/explorer/index.html index e689d0e47..c00b0e4ac 100644 --- a/frontend/explorer/index.html +++ b/frontend/explorer/index.html @@ -4,10 +4,14 @@ - - Explorer + + + + + + - - - - diff --git a/frontend/eyeballing/.env.production b/frontend/eyeballing/.env.production index 0237c64e6..cb97579f1 100644 --- a/frontend/eyeballing/.env.production +++ b/frontend/eyeballing/.env.production @@ -1,3 +1,3 @@ NODE_PATH=./src -PUBLIC_URL=/eyeballing/ -REACT_APP_VISIOMATIC_CREDENTIAL=true \ No newline at end of file +PUBLIC_URL=/tile_viewer/ +REACT_APP_VISIOMATIC_CREDENTIAL=false \ No newline at end of file diff --git a/frontend/eyeballing/public/favicon.ico b/frontend/eyeballing/public/favicon.ico deleted file mode 100755 index a11777cc471a4344702741ab1c8a588998b1311a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3870 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ diff --git a/frontend/eyeballing/public/favicon.png b/frontend/eyeballing/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..d5a5331be222f0e0e65629205497fd545e48ae07 GIT binary patch literal 20745 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 RQ!I} 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�G-*Rhu9Lb{Hx6Ms0u6>M^(G~Q( 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 literal 0 HcmV?d00001 diff --git a/frontend/eyeballing/public/index.html b/frontend/eyeballing/public/index.html index 6361f2cd4..fe7479e21 100755 --- a/frontend/eyeballing/public/index.html +++ b/frontend/eyeballing/public/index.html @@ -3,7 +3,12 @@ - + Tile Viewer + + + + + - - - - - Tile Viewer diff --git a/frontend/eyeballing/public/visiomatic/visiomatic/dist/visiomatic-src.js b/frontend/eyeballing/public/visiomatic/visiomatic/dist/visiomatic-src.js index 11e671a6a..6d1445bbd 100644 --- a/frontend/eyeballing/public/visiomatic/visiomatic/dist/visiomatic-src.js +++ b/frontend/eyeballing/public/visiomatic/visiomatic/dist/visiomatic-src.js @@ -38,394 +38,357 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ L.Projection.WCS = L.Class.extend({ - bounds: L.bounds([-0.5, -0.5], [0.5, 0.5]), - - // LatLng [deg] -> Point - project: function (latlng) { - var phiTheta = this._raDecToPhiTheta( - this.celsysflag ? this.eqToCelsys(latlng) : latlng - ); - phiTheta.lat = this._thetaToR(phiTheta.lat); - return this._redToPix(this._phiRToRed(phiTheta)); - }, - - // Point -> LatLng [deg] - unproject: function (point) { - var phiTheta = this._redToPhiR(this._pixToRed(point)); - phiTheta.lat = this._rToTheta(phiTheta.lat); - var latlng = this._phiThetaToRADec(phiTheta); - if (latlng.lng < -180.0) { - latlng.lng += 360.0; - } - return this.celsysflag ? this.celsysToEq(latlng) : latlng; - }, - - // Set up native pole - _natpole: function () { - var deg = Math.PI / 180.0, - projparam = this.projparam, - natpole = L.latLng(90.0, 180.0); - // Special case of fiducial point lying at the native pole - if (projparam.natrval.lat === 90.0) { - if (projparam.natpole.lng === 999.0) { - natpole.lng = 180.0; - } - natpole.lat = projparam.crval.lat; - } else if (projparam.natpole.lng === 999.0) { - natpole.lng = projparam.crval.lat < projparam.natrval.lat ? 180.0 : 0.0; - } - - return natpole; - }, - - // Set up celestial pole - _cpole: function () { - var deg = Math.PI / 180.0, - projparam = this.projparam, - dphip = projparam.natpole.lng - projparam.natrval.lng, - cdphip = Math.cos(dphip * deg), - sdphip = Math.sin(dphip * deg), - ct0 = Math.cos(projparam.natrval.lat * deg), - st0 = Math.sin(projparam.natrval.lat * deg), - cd0 = Math.cos(projparam.crval.lat * deg), - sd0 = Math.sin(projparam.crval.lat * deg), - deltap = Math.atan2(st0, ct0 * cdphip) / deg, - ddeltap = - Math.acos(sd0 / Math.sqrt(1.0 - ct0 * ct0 * sdphip * sdphip)) / deg, - deltap1 = deltap + ddeltap, - deltap2 = deltap - ddeltap; - if (deltap1 < -180.0) { - deltap1 += 360.0; - } else if (deltap1 > 180.0) { - deltap1 -= 360.0; - } - if (deltap2 < -180.0) { - deltap2 += 360.0; - } else if (deltap2 > 180.0) { - deltap2 -= 360.0; - } - if (deltap1 > 90.0) { - deltap = deltap2; - } else if (deltap2 < -90.0) { - deltap = deltap1; - } else { - deltap = - Math.abs(deltap1 - projparam.natpole.lat) < - Math.abs(deltap2 - projparam.natpole.lat) - ? deltap1 - : deltap2; - } - var alphap = - Math.abs(projparam.crval.lat) === 90.0 - ? projparam.crval.lng - : deltap === 90.0 - ? projparam.crval.lng + - projparam.natpole.lng - - projparam.natrval.lng - - 180.0 - : deltap === -90.0 - ? projparam.crval.lng - projparam.natpole.lng + projparam.natrval.lng - : projparam.crval.lng - - Math.atan2( - (sdphip * ct0) / cd0, - (st0 - Math.sin(deltap * deg) * sd0) / - (Math.cos(deltap * deg) * cd0) - ) / - deg; - return L.latLng(deltap, alphap); - }, - - // (phi,theta) [rad] -> RA, Dec [deg] for zenithal projections. - _phiThetaToRADec: function (phiTheta) { - var projparam = this.projparam, - deg = Math.PI / 180.0, - rad = 180.0 / Math.PI, - t = phiTheta.lat * deg, - ct = Math.cos(t), - st = Math.sin(t), - dp = projparam.cpole.lat * deg, - cdp = Math.cos(dp), - sdp = Math.sin(dp), - dphi = (phiTheta.lng - projparam.natpole.lng) * deg, - cdphi = Math.cos(dphi), - asinarg = st * sdp + ct * cdp * cdphi; - if (asinarg > 1.0) { - asinarg = 1.0; - } else if (asinarg < -1.0) { - asinarg = -1.0; - } - return L.latLng( - Math.asin(asinarg) * rad, - projparam.cpole.lng + - Math.atan2(-ct * Math.sin(dphi), st * cdp - ct * sdp * cdphi) * rad - ); - }, - - // (RA, Dec) [deg] -> (phi,theta) [rad] for zenithal projections. - _raDecToPhiTheta: function (raDec) { - var projparam = this.projparam, - deg = Math.PI / 180.0, - rad = 180.0 / Math.PI, - da = (raDec.lng - projparam.cpole.lng) * deg, - cda = Math.cos(da), - sda = Math.sin(da), - d = raDec.lat * deg, - cd = Math.cos(d), - sd = Math.sin(d), - dp = projparam.cpole.lat * deg, - cdp = Math.cos(dp), - sdp = Math.sin(dp), - asinarg = sd * sdp + cd * cdp * cda, - phitheta = L.latLng( - Math.asin(asinarg > 1.0 ? 1.0 : asinarg < -1.0 ? -1.0 : asinarg) * rad, - projparam.natpole.lng + - Math.atan2(-cd * sda, sd * cdp - cd * sdp * cda) * rad - ); - if (phitheta.lng > 180.0) { - phitheta.lng -= 360.0; - } else if (phitheta.lng < -180.0) { - phitheta.lng += 360.0; - } - return phitheta; - }, - - // Convert from pixel to reduced coordinates. - _pixToRed: function (pix) { - var projparam = this.projparam, - cd = projparam.cd, - red = pix.subtract(projparam.crpix); - return L.point( - red.x * cd[0][0] + red.y * cd[0][1], - red.x * cd[1][0] + red.y * cd[1][1] - ); - }, - - // Convert from reduced to pixel coordinates. - _redToPix: function (red) { - var projparam = this.projparam, - cdinv = projparam.cdinv; - return L.point( - red.x * cdinv[0][0] + red.y * cdinv[0][1], - red.x * cdinv[1][0] + red.y * cdinv[1][1] - ).add(projparam.crpix); - }, - - // Compute the CD matrix invert. - _invertCD: function (cd) { - var detinv = 1.0 / (cd[0][0] * cd[1][1] - cd[0][1] * cd[1][0]); - return [ - [cd[1][1] * detinv, -cd[0][1] * detinv], - [-cd[1][0] * detinv, cd[0][0] * detinv], - ]; - }, + + bounds: L.bounds([-0.5, -0.5], [0.5, 0.5]), + + // LatLng [deg] -> Point + project: function (latlng) { + var phiTheta = this._raDecToPhiTheta(this.celsysflag ? + this.eqToCelsys(latlng) : latlng); + phiTheta.lat = this._thetaToR(phiTheta.lat); + return this._redToPix(this._phiRToRed(phiTheta)); + }, + + // Point -> LatLng [deg] + unproject: function (point) { + var phiTheta = this._redToPhiR(this._pixToRed(point)); + phiTheta.lat = this._rToTheta(phiTheta.lat); + var latlng = this._phiThetaToRADec(phiTheta); + if (latlng.lng < -180.0) { + latlng.lng += 360.0; + } + return this.celsysflag ? this.celsysToEq(latlng) : latlng; + }, + + // Set up native pole + _natpole: function () { + var deg = Math.PI / 180.0, + projparam = this.projparam, + natpole = L.latLng(90.0, 180.0); + // Special case of fiducial point lying at the native pole + if (projparam.natrval.lat === 90.0) { + if (projparam.natpole.lng === 999.0) { + natpole.lng = 180.0; + } + natpole.lat = projparam.crval.lat; + } else if (projparam.natpole.lng === 999.0) { + natpole.lng = (projparam.crval.lat < projparam.natrval.lat) ? 180.0 : 0.0; + } + + return natpole; + }, + + // Set up celestial pole + _cpole: function () { + var deg = Math.PI / 180.0, + projparam = this.projparam, + dphip = projparam.natpole.lng - projparam.natrval.lng, + cdphip = Math.cos(dphip * deg), + sdphip = Math.sin(dphip * deg), + ct0 = Math.cos(projparam.natrval.lat * deg), + st0 = Math.sin(projparam.natrval.lat * deg), + cd0 = Math.cos(projparam.crval.lat * deg), + sd0 = Math.sin(projparam.crval.lat * deg), + deltap = Math.atan2(st0, ct0 * cdphip) / deg, + ddeltap = Math.acos(sd0 / Math.sqrt(1.0 - ct0 * ct0 * sdphip * sdphip)) / deg, + deltap1 = deltap + ddeltap, + deltap2 = deltap - ddeltap; + if (deltap1 < -180.0) { + deltap1 += 360.0; + } else if (deltap1 > 180.0) { + deltap1 -= 360.0; + } + if (deltap2 < -180.0) { + deltap2 += 360.0; + } else if (deltap2 > 180.0) { + deltap2 -= 360.0; + } + if (deltap1 > 90.0) { + deltap = deltap2; + } else if (deltap2 < -90.0) { + deltap = deltap1; + } else { + deltap = (Math.abs(deltap1 - projparam.natpole.lat) < + Math.abs(deltap2 - projparam.natpole.lat)) ? deltap1 : deltap2; + } + var alphap = Math.abs(projparam.crval.lat) === 90.0 ? projparam.crval.lng + : (deltap === 90.0 ? projparam.crval.lng + projparam.natpole.lng - + projparam.natrval.lng - 180.0 + : (deltap === -90.0 ? projparam.crval.lng - projparam.natpole.lng + + projparam.natrval.lng + : projparam.crval.lng - Math.atan2(sdphip * ct0 / cd0, + (st0 - Math.sin(deltap * deg) * sd0) / + (Math.cos(deltap * deg) * cd0)) / deg)); + return L.latLng(deltap, alphap); + }, + + // (phi,theta) [rad] -> RA, Dec [deg] for zenithal projections. + _phiThetaToRADec: function (phiTheta) { + var projparam = this.projparam, + deg = Math.PI / 180.0, + rad = 180.0 / Math.PI, + t = phiTheta.lat * deg, + ct = Math.cos(t), + st = Math.sin(t), + dp = projparam.cpole.lat * deg, + cdp = Math.cos(dp), + sdp = Math.sin(dp), + dphi = (phiTheta.lng - projparam.natpole.lng) * deg, + cdphi = Math.cos(dphi), + asinarg = st * sdp + ct * cdp * cdphi; + if (asinarg > 1.0) { + asinarg = 1.0; + } else if (asinarg < -1.0) { + asinarg = -1.0; + } + return L.latLng(Math.asin(asinarg) * rad, + projparam.cpole.lng + Math.atan2(- ct * Math.sin(dphi), + st * cdp - ct * sdp * cdphi) * rad); + }, + + // (RA, Dec) [deg] -> (phi,theta) [rad] for zenithal projections. + _raDecToPhiTheta: function (raDec) { + var projparam = this.projparam, + deg = Math.PI / 180.0, + rad = 180.0 / Math.PI, + da = (raDec.lng - projparam.cpole.lng) * deg, + cda = Math.cos(da), + sda = Math.sin(da), + d = raDec.lat * deg, + cd = Math.cos(d), + sd = Math.sin(d), + dp = projparam.cpole.lat * deg, + cdp = Math.cos(dp), + sdp = Math.sin(dp), + asinarg = sd * sdp + cd * cdp * cda, + phitheta = L.latLng(Math.asin(asinarg > 1.0 ? 1.0 + : (asinarg < -1.0 ? -1.0 : asinarg)) * rad, + projparam.natpole.lng + Math.atan2(- cd * sda, + sd * cdp - cd * sdp * cda) * rad); + if (phitheta.lng > 180.0) { + phitheta.lng -= 360.0; + } else if (phitheta.lng < -180.0) { + phitheta.lng += 360.0; + } + return phitheta; + }, + + // Convert from pixel to reduced coordinates. + _pixToRed: function (pix) { + var projparam = this.projparam, + cd = projparam.cd, + red = pix.subtract(projparam.crpix); + return L.point(red.x * cd[0][0] + red.y * cd[0][1], + red.x * cd[1][0] + red.y * cd[1][1]); + }, + + // Convert from reduced to pixel coordinates. + _redToPix: function (red) { + var projparam = this.projparam, + cdinv = projparam.cdinv; + return L.point(red.x * cdinv[0][0] + red.y * cdinv[0][1], + red.x * cdinv[1][0] + red.y * cdinv[1][1]).add(projparam.crpix); + }, + + // Compute the CD matrix invert. + _invertCD: function (cd) { + var detinv = 1.0 / (cd[0][0] * cd[1][1] - cd[0][1] * cd[1][0]); + return [[cd[1][1] * detinv, -cd[0][1] * detinv], + [-cd[1][0] * detinv, cd[0][0] * detinv]]; + } }); L.Projection.WCS.PIX = L.Projection.WCS.extend({ - code: 'PIX', - - _paramInit: function (projparam) { - this.projparam = projparam; - projparam.cdinv = this._invertCD(projparam.cd); - projparam.cpole = projparam.crval; - this.bounds = L.bounds( - [0.5, this.projparam.naxis.y - 0.5], - [this.projparam.naxis.x - 0.5, 0.5] - ); - }, - - project: function (latlng) { - return L.point(latlng.lng, latlng.lat); - }, - - unproject: function (point) { - return L.latLng(point.y, point.x); - }, + code: 'PIX', + + _paramInit: function (projparam) { + this.projparam = projparam; + projparam.cdinv = this._invertCD(projparam.cd); + projparam.cpole = projparam.crval; + this.bounds = L.bounds([0.5, this.projparam.naxis.y - 0.5], [this.projparam.naxis.x - 0.5, 0.5]); + }, + + project: function (latlng) { + return L.point(latlng.lng, latlng.lat); + }, + + unproject: function (point) { + return L.latLng(point.y, point.x); + } }); L.Projection.WCS.zenithal = L.Projection.WCS.extend({ - _paramInit: function (projparam) { - this.projparam = projparam; - projparam.cdinv = this._invertCD(projparam.cd); - projparam.natrval = L.latLng(90.0, 0.0); - projparam.natpole = this._natpole(); - projparam.cpole = this._cpole(); - }, - - // (x, y) ["deg"] -> \phi, r [deg] for zenithal projections. - _redToPhiR: function (red) { - return L.latLng( - Math.sqrt(red.x * red.x + red.y * red.y), - (Math.atan2(red.x, -red.y) * 180.0) / Math.PI - ); - }, - - // \phi, r [deg] -> (x, y) ["deg"] for zenithal projections. - _phiRToRed: function (phiR) { - var deg = Math.PI / 180.0, - p = phiR.lng * deg; - return new L.Point(phiR.lat * Math.sin(p), -phiR.lat * Math.cos(p)); - }, + + _paramInit: function (projparam) { + this.projparam = projparam; + projparam.cdinv = this._invertCD(projparam.cd); + projparam.natrval = L.latLng(90.0, 0.0); + projparam.natpole = this._natpole(); + projparam.cpole = this._cpole(); + }, + + // (x, y) ["deg"] -> \phi, r [deg] for zenithal projections. + _redToPhiR: function (red) { + return L.latLng(Math.sqrt(red.x * red.x + red.y * red.y), + Math.atan2(red.x, - red.y) * 180.0 / Math.PI); + }, + + // \phi, r [deg] -> (x, y) ["deg"] for zenithal projections. + _phiRToRed: function (phiR) { + var deg = Math.PI / 180.0, + p = phiR.lng * deg; + return new L.Point(phiR.lat * Math.sin(p), - phiR.lat * Math.cos(p)); + } }); L.Projection.WCS.TAN = L.Projection.WCS.zenithal.extend({ - code: 'TAN', + code: 'TAN', - _rToTheta: function (r) { - return (Math.atan2(180.0, Math.PI * r) * 180.0) / Math.PI; - }, + _rToTheta: function (r) { + return Math.atan2(180.0, Math.PI * r) * 180.0 / Math.PI; + }, - _thetaToR: function (theta) { - return (Math.tan(((90.0 - theta) * Math.PI) / 180.0) * 180.0) / Math.PI; - }, + _thetaToR: function (theta) { + return Math.tan((90.0 - theta) * Math.PI / 180.0) * 180.0 / Math.PI; + } }); L.Projection.WCS.ZEA = L.Projection.WCS.zenithal.extend({ - code: 'ZEA', - - _rToTheta: function (r) { - var rr = (r * Math.PI) / 360.0; - if (Math.abs(rr) < 1.0) { - return 90.0 - (2.0 * Math.asin(rr) * 180.0) / Math.PI; - } else { - return 90.0; - } - }, - - _thetaToR: function (theta) { - return (Math.sin(((90.0 - theta) * Math.PI) / 360.0) * 360.0) / Math.PI; - }, + code: 'ZEA', + + _rToTheta: function (r) { + var rr = r * Math.PI / 360.0; + if (Math.abs(rr) < 1.0) { + return 90.0 - 2.0 * Math.asin(rr) * 180.0 / Math.PI; + } else { + return 90.0; + } + }, + + _thetaToR: function (theta) { + return Math.sin((90.0 - theta) * Math.PI / 360.0) * 360.0 / Math.PI; + } + }); L.Projection.WCS.cylindrical = L.Projection.WCS.extend({ - _paramInit: function (projparam) { - var deg = Math.PI / 180.0; - this.projparam = projparam; - projparam.cdinv = this._invertCD(projparam.cd); - projparam.lambda = projparam.pv[1][1]; - if (projparam.lambda === 0.0) { - projparam.lambda = 1.0; - } - projparam.natrval = L.latLng(0.0, 0.0); - projparam.natpole = this._natpole(); - projparam.cpole = this._cpole(); - }, - - _rToTheta: function (r) { - return r; - }, - - _thetaToR: function (theta) { - return theta; - }, + + _paramInit: function (projparam) { + var deg = Math.PI / 180.0; + this.projparam = projparam; + projparam.cdinv = this._invertCD(projparam.cd); + projparam.lambda = projparam.pv[1][1]; + if (projparam.lambda === 0.0) { projparam.lambda = 1.0; } + projparam.natrval = L.latLng(0.0, 0.0); + projparam.natpole = this._natpole(); + projparam.cpole = this._cpole(); + }, + + _rToTheta: function (r) { + return r; + }, + + _thetaToR: function (theta) { + return theta; + } + }); L.Projection.WCS.CAR = L.Projection.WCS.cylindrical.extend({ - // (x, y) ["deg"] -> \phi, r [deg] for CAR projections. - _redToPhiR: function (red) { - return L.latLng(red.y, red.x); - }, - - // \phi, r [deg] -> (x, y) ["deg"] for CAR projections. - _phiRToRed: function (phiR) { - return L.point(phiR.lng, phiR.lat); - }, + + // (x, y) ["deg"] -> \phi, r [deg] for CAR projections. + _redToPhiR: function (red) { + return L.latLng(red.y, red.x); + }, + + // \phi, r [deg] -> (x, y) ["deg"] for CAR projections. + _phiRToRed: function (phiR) { + return L.point(phiR.lng, phiR.lat); + } }); L.Projection.WCS.CEA = L.Projection.WCS.cylindrical.extend({ - // (x, y) ["deg"] -> \phi, r [deg] for CEA projections. - _redToPhiR: function (red) { - var deg = Math.PI / 180.0, - slat = red.y * this.projparam.lambda * deg; - return L.latLng( - slat > -1.0 ? (slat < 1.0 ? Math.asin(slat) / deg : 90.0) : -90.0, - red.x - ); - }, - - // \phi, r [deg] -> (x, y) ["deg"] for CEA projections. - _phiRToRed: function (phiR) { - var deg = Math.PI / 180.0; - return L.point( - phiR.lng, - Math.sin(phiR.lat * deg) / (this.projparam.lambda * deg) - ); - }, + + // (x, y) ["deg"] -> \phi, r [deg] for CEA projections. + _redToPhiR: function (red) { + var deg = Math.PI / 180.0, + slat = red.y * this.projparam.lambda * deg; + return L.latLng(slat > -1.0 ? + (slat < 1.0 ? Math.asin(slat) / deg : 90.0) : -90.0, red.x); + }, + + // \phi, r [deg] -> (x, y) ["deg"] for CEA projections. + _phiRToRed: function (phiR) { + var deg = Math.PI / 180.0; + return L.point(phiR.lng, + Math.sin(phiR.lat * deg) / (this.projparam.lambda * deg)); + } }); L.Projection.WCS.conical = L.Projection.WCS.extend({ - // (x, y) ["deg"] -> \phi, r [deg] for conical projections. - _redToPhiR: function (red) { - var deg = Math.PI / 180.0, - projparam = this.projparam, - dy = projparam.y0 - red.y, - rTheta = projparam.sthetaA * Math.sqrt(red.x * red.x + dy * dy); - return L.latLng( - rTheta, - Math.atan2(red.x / rTheta, dy / rTheta) / projparam.c / deg - ); - }, - - // \phi, r [deg] -> (x, y) ["deg"] for conical projections. - _phiRToRed: function (phiR) { - var deg = Math.PI / 180.0, - p = this.projparam.c * phiR.lng * deg; - return L.point( - phiR.lat * Math.sin(p), - -phiR.lat * Math.cos(p) + this.projparam.y0 - ); - }, + + // (x, y) ["deg"] -> \phi, r [deg] for conical projections. + _redToPhiR: function (red) { + var deg = Math.PI / 180.0, + projparam = this.projparam, + dy = projparam.y0 - red.y, + rTheta = projparam.sthetaA * Math.sqrt(red.x * red.x + dy * dy); + return L.latLng(rTheta, Math.atan2(red.x / rTheta, dy / rTheta) / projparam.c / deg); + }, + + // \phi, r [deg] -> (x, y) ["deg"] for conical projections. + _phiRToRed: function (phiR) { + var deg = Math.PI / 180.0, + p = this.projparam.c * phiR.lng * deg; + return L.point(phiR.lat * Math.sin(p), - phiR.lat * Math.cos(p) + this.projparam.y0); + } }); L.Projection.WCS.COE = L.Projection.WCS.conical.extend({ - _paramInit: function (projparam) { - var deg = Math.PI / 180.0; - this.projparam = projparam; - projparam.cdinv = this._invertCD(projparam.cd); - projparam.thetaA = projparam.pv[1][1]; - projparam.eta = projparam.pv[1][2]; - projparam.sthetaA = projparam.thetaA >= 0.0 ? 1.0 : -1.0; - var theta1 = projparam.thetaA - projparam.eta, - theta2 = projparam.thetaA + projparam.eta, - s1 = Math.sin(theta1 * deg), - s2 = Math.sin(theta2 * deg); - projparam.gamma = s1 + s2; - projparam.s1s2p1 = s1 * s2 + 1.0; - projparam.c = projparam.gamma / 2.0; - projparam.y0 = - ((2.0 / projparam.gamma) * - Math.sqrt( - projparam.s1s2p1 - projparam.gamma * Math.sin(projparam.thetaA * deg) - )) / - deg; - projparam.natrval = L.latLng(projparam.thetaA, 0.0); - projparam.natpole = this._natpole(); - projparam.cpole = this._cpole(); - }, - - _rToTheta: function (r) { - var deg = Math.PI / 180.0, - gamma = this.projparam.gamma, - sinarg = - this.projparam.s1s2p1 / gamma - (gamma * r * r * deg * deg) / 4.0; - if (sinarg < -1.0) { - sinarg = -1.0; - } else if (sinarg > 1.0) { - sinarg = 1.0; - } - return Math.asin(sinarg) / deg; - }, - - _thetaToR: function (theta) { - var deg = Math.PI / 180.0, - gamma = this.projparam.gamma; - return ( - ((2.0 / gamma) * - Math.sqrt(this.projparam.s1s2p1 - gamma * Math.sin(theta * deg))) / - deg - ); - }, + + _paramInit: function (projparam) { + var deg = Math.PI / 180.0; + this.projparam = projparam; + projparam.cdinv = this._invertCD(projparam.cd); + projparam.thetaA = projparam.pv[1][1]; + projparam.eta = projparam.pv[1][2]; + projparam.sthetaA = projparam.thetaA >= 0.0 ? 1.0 : -1.0; + var theta1 = projparam.thetaA - projparam.eta, + theta2 = projparam.thetaA + projparam.eta, + s1 = Math.sin(theta1 * deg), + s2 = Math.sin(theta2 * deg); + projparam.gamma = s1 + s2; + projparam.s1s2p1 = s1 * s2 + 1.0; + projparam.c = projparam.gamma / 2.0; + projparam.y0 = 2.0 / projparam.gamma * Math.sqrt(projparam.s1s2p1 - + projparam.gamma * Math.sin(projparam.thetaA * deg)) / deg; + projparam.natrval = L.latLng(projparam.thetaA, 0.0); + projparam.natpole = this._natpole(); + projparam.cpole = this._cpole(); + }, + + _rToTheta: function (r) { + var deg = Math.PI / 180.0, + gamma = this.projparam.gamma, + sinarg = this.projparam.s1s2p1 / gamma - gamma * r * r * deg * deg / 4.0; + if (sinarg < -1.0) { + sinarg = -1.0; + } else if (sinarg > 1.0) { + sinarg = 1.0; + } + return Math.asin(sinarg) / deg; + }, + + _thetaToR: function (theta) { + var deg = Math.PI / 180.0, + gamma = this.projparam.gamma; + return 2.0 / gamma * Math.sqrt(this.projparam.s1s2p1 - gamma * Math.sin(theta * deg)) / deg; + } + }); + + /* # L.WCS emulates the FITS WCS (World Coordinate System) popular among # the astronomical community (see http://www.atnf.csiro.au/people/mcalabre/WCS/) @@ -439,440 +402,333 @@ L.Projection.WCS.COE = L.Projection.WCS.conical.extend({ */ L.CRS.WCS = L.extend({}, L.CRS, { - code: 'WCS', - - options: { - nzoom: 9, - tileSize: [256, 256], - nativeCelsys: false, // If true, world coordinates are returned - // in the native celestial system - }, - - defaultparam: { - ctype: { x: 'PIXEL', y: 'PIXEL' }, - naxis: [256, 256], - crpix: [129, 129], - crval: [0.0, 0.0], // (\delta_0, \alpha_0) - // cpole: (equal to crval by default) // (\delta_p, \alpha_p) - cd: [[1.0, 0.0], [0.0, 1.0]], - natrval: [90.0, 0.0], // (\theta_0. \phi_0) - natpole: [90.0, 999.0], // (\theta_p, \phi_p) - pv: [ - [ - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - ], - [ - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - ], - ], - }, - - initialize: function (hdr, options) { - options = L.setOptions(this, options); - var defaultparam = this.defaultparam; - - this.tileSize = L.point(options.tileSize); - this.nzoom = options.nzoom; - this.ctype = { x: defaultparam.ctype.x, y: defaultparam.ctype.y }; - this.naxis = L.point(defaultparam.naxis, true); - this.projparam = new this._paramInit(defaultparam); - if (hdr) { - this._readWCS(hdr); - } - this._paramInit(options, this.projparam); - - // Identify the WCS projection type - switch (this.ctype.x.substr(5, 3)) { - case 'ZEA': - this.projection = new L.Projection.WCS.ZEA(); - this.pixelFlag = false; - this.infinite = true; - break; - case 'TAN': - this.projection = new L.Projection.WCS.TAN(); - this.pixelFlag = false; - this.infinite = true; - break; - case 'CAR': - this.projection = new L.Projection.WCS.CAR(); - this.pixelFlag = false; - this.infinite = true; - break; - case 'CEA': - this.projection = new L.Projection.WCS.CEA(); - this.pixelFlag = false; - this.infinite = true; - break; - case 'COE': - this.projection = new L.Projection.WCS.COE(); - this.pixelFlag = false; - this.infinite = true; - break; - default: - this.projection = new L.Projection.WCS.PIX(); - this.pixelFlag = true; - this.infinite = false; - // Center on image if WCS is in pixels - if (!this.options.crval) { - this.projparam.crval = L.latLng( - (this.naxis.y + 1.0) / 2.0, - (this.naxis.x + 1.0) / 2.0 - ); - } - this.wrapLng = [0.5, this.naxis.x - 0.5]; - this.wrapLat = [this.naxis.y - 0.5, 0.5]; - break; - } - - if (!this.pixelFlag) { - // Identify the native celestial coordinate system - switch (this.ctype.x.substr(0, 1)) { - case 'G': - this.celsyscode = 'galactic'; - break; - case 'E': - this.celsyscode = 'ecliptic'; - break; - case 'S': - this.celsyscode = 'supergalactic'; - break; - default: - this.celsyscode = 'equatorial'; - break; - } - - if (this.celsyscode !== 'equatorial') { - this.projparam.celsysmat = this._celsysmatInit(this.celsyscode); - this.projection.celsysToEq = this.celsysToEq; - this.projection.eqToCelsys = this.eqToCelsys; - this.forceNativeCelsys = this.options.nativeCelsys === true; - this.projection.celsysflag = !this.forceNativeCelsys; - } - } - - this.transformation = new L.Transformation( - 1.0, - -0.5, - -1.0, - this.naxis.y + 0.5 - ); - this.projection._paramInit(this.projparam); - this.code += ':' + this.projection.code; - }, - - // convert celestial (angular) coordinates to equatorial - celsysToEq: function (latlng) { - var cmat = this.projparam.celsysmat, - deg = Math.PI / 180.0, - invdeg = 180.0 / Math.PI, - a2 = latlng.lng * deg - cmat[1], - d2 = latlng.lat * deg, - sd2 = Math.sin(d2), - cd2cp = Math.cos(d2) * cmat[2], - sd = sd2 * cmat[3] - cd2cp * Math.cos(a2); - return L.latLng( - Math.asin(sd) * invdeg, - ((Math.atan2(cd2cp * Math.sin(a2), sd2 - sd * cmat[3]) + cmat[0]) * - invdeg + - 360.0) % - 360.0 - ); - }, - - // convert equatorial (angular) coordinates to celestial - eqToCelsys: function (latlng) { - var cmat = this.projparam.celsysmat, - deg = Math.PI / 180.0, - invdeg = 180.0 / Math.PI, - a = latlng.lng * deg - cmat[0], - sd = Math.sin(latlng.lat * deg), - cdcp = Math.cos(latlng.lat * deg) * cmat[2], - sd2 = sd * cmat[3] + cdcp * Math.cos(a); - return L.latLng( - Math.asin(sd2) * invdeg, - ((Math.atan2(cdcp * Math.sin(a), sd2 * cmat[3] - sd) + cmat[1]) * invdeg + - 360.0) % - 360.0 - ); - }, - - scale: function (zoom) { - return Math.pow(2, zoom - this.nzoom + 1); - }, - - zoom: function (scale) { - return Math.log(scale) / Math.LN2 + this.nzoom - 1; - }, - - // return the raw pixel scale in degrees - rawPixelScale: function (latlng) { - var p0 = this.projection.project(latlng), - latlngdx = this.projection.unproject(p0.add([10.0, 0.0])), - latlngdy = this.projection.unproject(p0.add([0.0, 10.0])), - dlngdx = latlngdx.lng - latlng.lng, - dlngdy = latlngdy.lng - latlng.lng; - - if (dlngdx > 180.0) { - dlngdx -= 360.0; - } else if (dlngdx < -180.0) { - dlngdx += 360.0; - } - if (dlngdy > 180.0) { - dlngdy -= 360.0; - } else if (dlngdy < -180.0) { - dlngdy += 360.0; - } - - return ( - 0.1 * - Math.sqrt( - Math.abs( - dlngdx * (latlngdy.lat - latlng.lat) - - dlngdy * (latlngdx.lat - latlng.lat) - ) * Math.cos((latlng.lat * Math.PI) / 180.0) - ) - ); - }, - - // return the current pixel scale in degrees - pixelScale: function (zoom, latlng) { - return this.rawPixelScale(latlng) / this.scale(zoom); - }, - - // return the zoom level that corresponds to the given FoV in degrees - fovToZoom: function (map, fov, latlng) { - var scale = this.rawPixelScale(latlng), - size = map.getSize(); - - if (fov < scale) { - fov = scale; - } - scale *= Math.sqrt(size.x * size.x + size.y * size.y); - return fov > 0.0 ? this.zoom(scale / fov) : this.nzoom - 1; - }, - - // return the FoV in degrees that corresponds to the given zoom level - zoomToFov: function (map, zoom, latlng) { - var size = map.getSize(), - scale = - this.rawPixelScale(latlng) * - Math.sqrt(size.x * size.x + size.y * size.y), - zscale = this.scale(zoom); - return zscale > 0.0 ? scale / zscale : scale; - }, - - distance: function (latlng1, latlng2) { - var rad = Math.PI / 180.0, - lat1 = latlng1.lat * rad, - lat2 = latlng2.lat * rad, - a = - Math.sin(lat1) * Math.sin(lat2) + - Math.cos(lat1) * - Math.cos(lat2) * - Math.cos((latlng2.lng - latlng1.lng) * rad); - - return (180.0 / Math.PI) * Math.acos(Math.min(a, 1)); - }, - - // Parse a string of coordinates. Return undefined if parsing failed - parseCoords: function (str) { - var result, latlng; - - // Try VisiOmatic sexagesimal first - latlng = L.IIPUtils.hmsDMSToLatLng(str); - if (typeof latlng === 'undefined') { - // Parse regular deg, deg. The heading "J" is to support the Sesame@CDS output - result = /(?:%J\s|^)([-+]?\d+\.?\d*)\s*[,\s]+\s*([-+]?\d+\.?\d*)/g.exec( - str - ); - if (result && result.length >= 3) { - latlng = L.latLng(Number(result[2]), Number(result[1])); - } - } - if (latlng) { - if (this.forceNativeCelsys) { - latlng = this.eqToCelsys(latlng); - } - return latlng; - } else { - return undefined; - } - }, - - // Initialize WCS parameters - _paramInit: function (newparam, param) { - if (!param) { - param = this; - } - if (newparam.naxis) { - param.naxis = L.point(newparam.naxis); - } - if (newparam.crval) { - param.crval = param.cpole = L.latLng(newparam.crval); - } - if (newparam.crpix) { - param.crpix = L.point(newparam.crpix); - } - if (newparam.cd) { - param.cd = [ - [newparam.cd[0][0], newparam.cd[0][1]], - [newparam.cd[1][0], newparam.cd[1][1]], - ]; - } - if (newparam.natrval) { - param.natrval = L.latLng(newparam.natrval); - } - if (newparam.natpole) { - param.natpole = L.latLng(newparam.natpole); - } - if (newparam.pv) { - param.pv = []; - param.pv[0] = newparam.pv[0].slice(); - param.pv[1] = newparam.pv[1].slice(); - } - }, - - // Generate a celestial coordinate system transformation matrix - _celsysmatInit: function (celcode) { - var deg = Math.PI / 180.0, - corig, - cpole, - cmat = []; - switch (celcode) { - case 'galactic': - corig = L.latLng(-28.93617242, 266.40499625); - cpole = L.latLng(27.1282512, 192.85948123); - break; - case 'ecliptic': - corig = L.latLng(0.0, 0.0); - cpole = L.latLng(66.99111111, 273.85261111); - break; - case 'supergalactic': - corig = L.latLng(59.52315, 42.29235); - cpole = L.latLng(15.7048, 283.7514); - break; - default: - corig = L.latLng(0.0, 0.0); - cpole = L.latLng(0.0, 0.0); - break; - } - cmat[0] = cpole.lng * deg; - cmat[1] = Math.asin( - Math.cos(corig.lat * deg) * Math.sin((cpole.lng - corig.lng) * deg) - ); - cmat[2] = Math.cos(cpole.lat * deg); - cmat[3] = Math.sin(cpole.lat * deg); - - return cmat; - }, - - // Read WCS information from a FITS header - _readWCS: function (hdr) { - var key = L.IIPUtils.readFITSKey, - projparam = this.projparam, - v; - if ((v = key('CTYPE1', hdr))) { - this.ctype.x = v; - } - if ((v = key('CTYPE2', hdr))) { - this.ctype.y = v; - } - if ((v = key('NAXIS1', hdr))) { - projparam.naxis.x = this.naxis.x = parseInt(v, 10); - } - if ((v = key('NAXIS2', hdr))) { - projparam.naxis.y = this.naxis.y = parseInt(v, 10); - } - if ((v = key('CRPIX1', hdr))) { - projparam.crpix.x = parseFloat(v, 10); - } - if ((v = key('CRPIX2', hdr))) { - projparam.crpix.y = parseFloat(v, 10); - } - if ((v = key('CRVAL1', hdr))) { - projparam.crval.lng = parseFloat(v, 10); - } - if ((v = key('CRVAL2', hdr))) { - projparam.crval.lat = parseFloat(v, 10); - } - if ((v = key('LONPOLE', hdr))) { - projparam.natpole.lng = parseFloat(v, 10); - } - if ((v = key('LATPOLE', hdr))) { - projparam.natpol.lat = parseFloat(v, 10); - } - if ((v = key('CD1_1', hdr))) { - projparam.cd[0][0] = parseFloat(v, 10); - } - if ((v = key('CD1_2', hdr))) { - projparam.cd[0][1] = parseFloat(v, 10); - } - if ((v = key('CD2_1', hdr))) { - projparam.cd[1][0] = parseFloat(v, 10); - } - if ((v = key('CD2_2', hdr))) { - projparam.cd[1][1] = parseFloat(v, 10); - } - for (var d = 0; d < 2; d++) { - for (var j = 0; j < 20; j++) { - if ((v = key('PV' + (d + 1) + '_' + j, hdr))) { - projparam.pv[d][j] = parseFloat(v, 10); - } - } - } - }, - - _deltaLng: function (latLng, latLng0) { - var dlng = latLng.lng - latLng0.lng; - - return dlng > 180.0 ? dlng - 360.0 : dlng < -180.0 ? dlng + 360.0 : dlng; - }, + code: 'WCS', + + options: { + nzoom: 9, + tileSize: [256, 256], + nativeCelsys: false // If true, world coordinates are returned + // in the native celestial system + }, + + defaultparam: { + ctype: { x: 'PIXEL', y: 'PIXEL' }, + naxis: [256, 256], + crpix: [129, 129], + crval: [0.0, 0.0], // (\delta_0, \alpha_0) + // cpole: (equal to crval by default) // (\delta_p, \alpha_p) + cd: [[1.0, 0.0], [0.0, 1.0]], + natrval: [90.0, 0.0], // (\theta_0. \phi_0) + natpole: [90.0, 999.0], // (\theta_p, \phi_p) + pv: [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]] + }, + + initialize: function (hdr, options) { + options = L.setOptions(this, options); + var defaultparam = this.defaultparam; + + this.tileSize = L.point(options.tileSize); + this.nzoom = options.nzoom; + this.ctype = { x: defaultparam.ctype.x, y: defaultparam.ctype.y }; + this.naxis = L.point(defaultparam.naxis, true); + this.projparam = new this._paramInit(defaultparam); + if (hdr) { + this._readWCS(hdr); + } + this._paramInit(options, this.projparam); + + // Identify the WCS projection type + switch (this.ctype.x.substr(5, 3)) { + case 'ZEA': + this.projection = new L.Projection.WCS.ZEA(); + this.pixelFlag = false; + this.infinite = true; + break; + case 'TAN': + this.projection = new L.Projection.WCS.TAN(); + this.pixelFlag = false; + this.infinite = true; + break; + case 'CAR': + this.projection = new L.Projection.WCS.CAR(); + this.pixelFlag = false; + this.infinite = true; + break; + case 'CEA': + this.projection = new L.Projection.WCS.CEA(); + this.pixelFlag = false; + this.infinite = true; + break; + case 'COE': + this.projection = new L.Projection.WCS.COE(); + this.pixelFlag = false; + this.infinite = true; + break; + default: + this.projection = new L.Projection.WCS.PIX(); + this.pixelFlag = true; + this.infinite = false; + // Center on image if WCS is in pixels + if (!this.options.crval) { + this.projparam.crval = L.latLng((this.naxis.y + 1.0) / 2.0, + (this.naxis.x + 1.0) / 2.0); + } + this.wrapLng = [0.5, this.naxis.x - 0.5]; + this.wrapLat = [this.naxis.y - 0.5, 0.5]; + break; + } + + if (!this.pixelFlag) { + // Identify the native celestial coordinate system + switch (this.ctype.x.substr(0, 1)) { + case 'G': + this.celsyscode = 'galactic'; + break; + case 'E': + this.celsyscode = 'ecliptic'; + break; + case 'S': + this.celsyscode = 'supergalactic'; + break; + default: + this.celsyscode = 'equatorial'; + break; + } + + if (this.celsyscode !== 'equatorial') { + this.projparam.celsysmat = this._celsysmatInit(this.celsyscode); + this.projection.celsysToEq = this.celsysToEq; + this.projection.eqToCelsys = this.eqToCelsys; + this.forceNativeCelsys = (this.options.nativeCelsys === true); + this.projection.celsysflag = !this.forceNativeCelsys; + } + } + + this.transformation = new L.Transformation(1.0, -0.5, -1.0, this.naxis.y + 0.5); + this.projection._paramInit(this.projparam); + this.code += ':' + this.projection.code; + }, + + // convert celestial (angular) coordinates to equatorial + celsysToEq: function (latlng) { + var cmat = this.projparam.celsysmat, + deg = Math.PI / 180.0, + invdeg = 180.0 / Math.PI, + a2 = latlng.lng * deg - cmat[1], + d2 = latlng.lat * deg, + sd2 = Math.sin(d2), + cd2cp = Math.cos(d2) * cmat[2], + sd = sd2 * cmat[3] - cd2cp * Math.cos(a2); + return L.latLng(Math.asin(sd) * invdeg, + ((Math.atan2(cd2cp * Math.sin(a2), sd2 - sd * cmat[3]) + + cmat[0]) * invdeg + 360.0) % 360.0); + }, + + // convert equatorial (angular) coordinates to celestial + eqToCelsys: function (latlng) { + var cmat = this.projparam.celsysmat, + deg = Math.PI / 180.0, + invdeg = 180.0 / Math.PI, + a = latlng.lng * deg - cmat[0], + sd = Math.sin(latlng.lat * deg), + cdcp = Math.cos(latlng.lat * deg) * cmat[2], + sd2 = sd * cmat[3] + cdcp * Math.cos(a); + return L.latLng(Math.asin(sd2) * invdeg, + ((Math.atan2(cdcp * Math.sin(a), sd2 * cmat[3] - sd) + + cmat[1]) * invdeg + 360.0) % 360.0); + }, + + + scale: function (zoom) { + return Math.pow(2, zoom - this.nzoom + 1); + }, + + zoom: function (scale) { + return Math.log(scale) / Math.LN2 + this.nzoom - 1; + }, + + // return the raw pixel scale in degrees + rawPixelScale: function (latlng) { + var p0 = this.projection.project(latlng), + latlngdx = this.projection.unproject(p0.add([10.0, 0.0])), + latlngdy = this.projection.unproject(p0.add([0.0, 10.0])), + dlngdx = latlngdx.lng - latlng.lng, + dlngdy = latlngdy.lng - latlng.lng; + + if (dlngdx > 180.0) { dlngdx -= 360.0; } + else if (dlngdx < -180.0) { dlngdx += 360.0; } + if (dlngdy > 180.0) { dlngdy -= 360.0; } + else if (dlngdy < -180.0) { dlngdy += 360.0; } + + return 0.1 * Math.sqrt(Math.abs((dlngdx * (latlngdy.lat - latlng.lat) - + dlngdy * (latlngdx.lat - latlng.lat))) * + Math.cos(latlng.lat * Math.PI / 180.0)); + }, + + // return the current pixel scale in degrees + pixelScale: function (zoom, latlng) { + return this.rawPixelScale(latlng) / this.scale(zoom); + }, + + // return the zoom level that corresponds to the given FoV in degrees + fovToZoom: function (map, fov, latlng) { + var scale = this.rawPixelScale(latlng), + size = map.getSize(); + + if (fov < scale) { fov = scale; } + scale *= Math.sqrt(size.x * size.x + size.y * size.y); + return fov > 0.0 ? this.zoom(scale / fov) : this.nzoom - 1; + }, + + // return the FoV in degrees that corresponds to the given zoom level + zoomToFov: function (map, zoom, latlng) { + var size = map.getSize(), + scale = this.rawPixelScale(latlng) * + Math.sqrt(size.x * size.x + size.y * size.y), + zscale = this.scale(zoom); + return zscale > 0.0 ? scale / zscale : scale; + }, + + distance: function (latlng1, latlng2) { + var rad = Math.PI / 180.0, + lat1 = latlng1.lat * rad, + lat2 = latlng2.lat * rad, + a = Math.sin(lat1) * Math.sin(lat2) + + Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad); + + return 180.0 / Math.PI * Math.acos(Math.min(a, 1)); + }, + + // Parse a string of coordinates. Return undefined if parsing failed + parseCoords: function (str) { + var result, latlng; + + // Try VisiOmatic sexagesimal first + latlng = L.IIPUtils.hmsDMSToLatLng(str); + if (typeof latlng === 'undefined') { + // Parse regular deg, deg. The heading "J" is to support the Sesame@CDS output + result = /(?:%J\s|^)([-+]?\d+\.?\d*)\s*[,\s]+\s*([-+]?\d+\.?\d*)/g.exec(str); + if (result && result.length >= 3) { + latlng = L.latLng(Number(result[2]), Number(result[1])); + } + } + if (latlng) { + if (this.forceNativeCelsys) { + latlng = this.eqToCelsys(latlng); + } + return latlng; + } else { + return undefined; + } + }, + + // Initialize WCS parameters + _paramInit: function (newparam, param) { + if (!param) { + param = this; + } + if (newparam.naxis) { + param.naxis = L.point(newparam.naxis); + } + if (newparam.crval) { + param.crval = param.cpole = L.latLng(newparam.crval); + } + if (newparam.crpix) { + param.crpix = L.point(newparam.crpix); + } + if (newparam.cd) { + param.cd = [[newparam.cd[0][0], newparam.cd[0][1]], + [newparam.cd[1][0], newparam.cd[1][1]]]; + } + if (newparam.natrval) { + param.natrval = L.latLng(newparam.natrval); + } + if (newparam.natpole) { + param.natpole = L.latLng(newparam.natpole); + } + if (newparam.pv) { + param.pv = []; + param.pv[0] = newparam.pv[0].slice(); + param.pv[1] = newparam.pv[1].slice(); + } + }, + + // Generate a celestial coordinate system transformation matrix + _celsysmatInit: function (celcode) { + var deg = Math.PI / 180.0, + corig, cpole, + cmat = []; + switch (celcode) { + case 'galactic': + corig = L.latLng(-28.93617242, 266.40499625); + cpole = L.latLng(27.12825120, 192.85948123); + break; + case 'ecliptic': + corig = L.latLng(0.0, 0.0); + cpole = L.latLng(66.99111111, 273.85261111); + break; + case 'supergalactic': + corig = L.latLng(59.52315, 42.29235); + cpole = L.latLng(15.70480, 283.7514); + break; + default: + corig = L.latLng(0.0, 0.0); + cpole = L.latLng(0.0, 0.0); + break; + } + cmat[0] = cpole.lng * deg; + cmat[1] = Math.asin(Math.cos(corig.lat * deg) * Math.sin((cpole.lng - corig.lng) * deg)); + cmat[2] = Math.cos(cpole.lat * deg); + cmat[3] = Math.sin(cpole.lat * deg); + + return cmat; + }, + + // Read WCS information from a FITS header + _readWCS: function (hdr) { + var key = L.IIPUtils.readFITSKey, + projparam = this.projparam, + v; + if ((v = key('CTYPE1', hdr))) { this.ctype.x = v; } + if ((v = key('CTYPE2', hdr))) { this.ctype.y = v; } + if ((v = key('NAXIS1', hdr))) { projparam.naxis.x = this.naxis.x = parseInt(v, 10); } + if ((v = key('NAXIS2', hdr))) { projparam.naxis.y = this.naxis.y = parseInt(v, 10); } + if ((v = key('CRPIX1', hdr))) { projparam.crpix.x = parseFloat(v, 10); } + if ((v = key('CRPIX2', hdr))) { projparam.crpix.y = parseFloat(v, 10); } + if ((v = key('CRVAL1', hdr))) { projparam.crval.lng = parseFloat(v, 10); } + if ((v = key('CRVAL2', hdr))) { projparam.crval.lat = parseFloat(v, 10); } + if ((v = key('LONPOLE', hdr))) { projparam.natpole.lng = parseFloat(v, 10); } + if ((v = key('LATPOLE', hdr))) { projparam.natpol.lat = parseFloat(v, 10); } + if ((v = key('CD1_1', hdr))) { projparam.cd[0][0] = parseFloat(v, 10); } + if ((v = key('CD1_2', hdr))) { projparam.cd[0][1] = parseFloat(v, 10); } + if ((v = key('CD2_1', hdr))) { projparam.cd[1][0] = parseFloat(v, 10); } + if ((v = key('CD2_2', hdr))) { projparam.cd[1][1] = parseFloat(v, 10); } + for (var d = 0; d < 2; d++) { + for (var j = 0; j < 20; j++) { + if ((v = key('PV' + (d + 1) + '_' + j, hdr))) { + projparam.pv[d][j] = parseFloat(v, 10); + } + } + } + }, + + _deltaLng: function (latLng, latLng0) { + var dlng = latLng.lng - latLng0.lng; + + return dlng > 180.0 ? dlng - 360.0 : (dlng < -180.0 ? dlng + 360.0 : dlng); + } }); L.CRS.WCS = L.Class.extend(L.CRS.WCS); L.CRS.wcs = function (options) { - return new L.CRS.WCS(options); + return new L.CRS.WCS(options); }; + /* # L.IIPUtils contains general utility methods # @@ -884,265 +740,227 @@ L.CRS.wcs = function (options) { # Last modified: 01/12/2017 */ L.IIPUtils = { - // Definitions for RegExp - REG_PDEC: '(\\d+\\.\\d*)', - REG_FLOAT: '([-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?)', - - // Ajax call to server - requestURL: function (url, purpose, action, context, timeout) { - var httpRequest; - - if (window.XMLHttpRequest) { - // Mozilla, Safari, ... - httpRequest = new XMLHttpRequest(); - } else if (window.ActiveXObject) { - // IE - try { - httpRequest = new ActiveXObject('Msxml2.XMLHTTP'); - } catch (e) { - try { - httpRequest = new ActiveXObject('Microsoft.XMLHTTP'); - } catch (e) { } - } - } - if (!httpRequest) { - alert('Giving up: Cannot create an XMLHTTP instance for ' + purpose); - return false; - } - if (timeout) { - httpRequest.timeout = timeout * 1000; // seconds -> milliseconds - httpRequest.ontimeout = function () { - alert('Time out while ' + purpose); - }; - } - httpRequest.open('GET', url); - - // Send Credrentials - if (context && context.options.credentials) { - // httpRequest.setRequestHeader('Access-Control-Allow-Origin', '*'); - httpRequest.withCredentials = true; - } - - // if request catalog need authenticate - if (context && context.options.authenticate === 'csrftoken') { - httpRequest.setRequestHeader('X-CSRFToken', this.getCookie('csrftoken')); - } - - if (action) { - httpRequest.onreadystatechange = function () { - action(context, httpRequest); - }; - } - httpRequest.send(); - }, - - // Return a dictionary of name/value pairs from a URL string, from - // http://stevenbenner.com/2010/03/javascript-regex-trick-parse-a-query-string-into-an-object/ - parseURL: function (url) { - var dict = {}; - url.replace(new RegExp('([^?=&]+)(=([^&]*))?', 'g'), function ( - $0, - $1, - $2, - $3 - ) { - dict[$1] = $3; - }); - return dict; - }, - - // Return a URL with an updated keyword/value queryString(from http://stackoverflow.com/a/5999118) - updateURL: function (url, keyword, value) { - var re = new RegExp('([?&])' + keyword + '=.*?(&|$)', 'i'), - separator = url.indexOf('?') !== -1 ? '&' : '?'; - - return url.match(re) - ? url.replace(re, '$1' + keyword + '=' + value + '$2') - : url + separator + keyword + '=' + value; - }, - - // Return the domain of a given URL (from http://stackoverflow.com/a/28054735) - checkDomain: function (url) { - if (url.indexOf('//') === 0) { - url = location.protocol + url; - } - return url - .toLowerCase() - .replace(/([a-z])?:\/\//, '$1') - .split('/')[0]; - }, - - // Check if a given URL is external (from http://stackoverflow.com/a/28054735) - isExternal: function (url) { - return ( - (url.indexOf(':') > -1 || url.indexOf('//') > -1) && - this.checkDomain(location.href) !== this.checkDomain(url) - ); - }, - - // Copy string to clipboard (from http://stackoverflow.com/a/33928558) - // Chrome 43+, Firefox 42+, Edge and Safari 10+ supported - copyToClipboard: function (text) { - if ( - document.queryCommandSupported && - document.queryCommandSupported('copy') - ) { - var textarea = document.createElement('textarea'); - textarea.textContent = text; - textarea.style.position = 'fixed'; // Prevent scrolling to bottom of page in MS Edge. - document.body.appendChild(textarea); - textarea.select(); - try { - return document.execCommand('copy'); // Security exception may be thrown by some browsers. - } catch (ex) { - console.warn('Copy to clipboard failed.', ex); - return false; - } finally { - document.body.removeChild(textarea); - } - } - }, - - // Add a short (<400ms) "flash" animation to an element - flashElement: function (elem) { - L.DomUtil.addClass(elem, 'leaflet-control-flash'); - setTimeout(function () { - L.DomUtil.removeClass(elem, 'leaflet-control-flash'); - }, 400); - }, - - // Read content of a FITS header keyword - readFITSKey: function (keyword, str) { - var key = keyword - .trim() - .toUpperCase() - .substr(0, 8), - nspace = 8 - key.length, - keyreg = new RegExp( - key + - (nspace > 0 ? '\\ {' + nspace.toString() + '}' : '') + - "=\\ *(?:'((?:\\ *[^'\\ ]+)*)\\ *'|([-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?))" - ), - match = keyreg.exec(str); - if (!match) { - return null; - } else if (match[1]) { - return match[1]; - } else { - return match[2]; - } - }, - - // Return the distance between two world coords latLng1 and latLng2 in degrees - distance: function (latlng1, latlng2) { - var d2r = Math.PI / 180.0, - lat1 = latlng1.lat * d2r, - lat2 = latlng2.lat * d2r, - dLat = lat2 - lat1, - dLon = (latlng2.lng - latlng1.lng) * d2r, - sin1 = Math.sin(dLat / 2), - sin2 = Math.sin(dLon / 2); - - var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2); - - return (Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) * 360.0) / Math.PI; - }, - - // Convert degrees to HMSDMS (DMS code from the Leaflet-Coordinates plug-in) - latLngToHMSDMS: function (latlng) { - var lng = (latlng.lng + 360.0) / 360.0; - lng = (lng - Math.floor(lng)) * 24.0; - var h = Math.floor(lng), - mf = (lng - h) * 60.0, - m = Math.floor(mf), - sf = (mf - m) * 60.0; - if (sf >= 60.0) { - m++; - sf = 0.0; - } - if (m === 60) { - h++; - m = 0; - } - var str = - (h < 10 ? '0' : '') + - h.toString() + - ':' + - (m < 10 ? '0' : '') + - m.toString() + - ':' + - (sf < 10.0 ? '0' : '') + - sf.toFixed(3), - lat = Math.abs(latlng.lat), - sgn = latlng.lat < 0.0 ? '-' : '+', - d = Math.floor(lat); - mf = (lat - d) * 60.0; - m = Math.floor(mf); - sf = (mf - m) * 60.0; - if (sf >= 60.0) { - m++; - sf = 0.0; - } - if (m === 60) { - h++; - m = 0; - } - return ( - str + - ' ' + - sgn + - (d < 10 ? '0' : '') + - d.toString() + - ':' + - (m < 10 ? '0' : '') + - m.toString() + - ':' + - (sf < 10.0 ? '0' : '') + - sf.toFixed(2) - ); - }, - - // Convert HMSDMS to degrees - hmsDMSToLatLng: function (str) { - var result; // Long regexp line (difficult to circumvent) - /* jshint ignore:start */ result = /^\s*(\d+)[h:](\d+)[m':](\d+\.?\d*)[s"]?\s*,?\s*([-+]?\d+)[d°:](\d+)[m':](\d+\.?\d*)[s"]?/g.exec( - str - ); - /* jshint ignore:end */ - if (result && result.length >= 7) { - var dd = Number(result[4]); - - return L.latLng( - (dd < 0.0 ? -1.0 : 1.0) * - (Math.abs(dd) + - Number(result[5]) / 60.0 + - Number(result[6]) / 3600.0), - Number(result[1]) * 15.0 + - Number(result[2]) / 4.0 + - Number(result[3]) / 240.0 - ); - } else { - return undefined; - } - }, - - // returns the value of a specified cookie (from http://www.w3schools.com/js/js_cookies.asp) - getCookie: function (cname) { - var name = cname + '='; - var ca = document.cookie.split(';'); - for (var i = 0; i < ca.length; i++) { - var c = ca[i]; - while (c.charAt(0) === ' ') { - c = c.substring(1); - } - if (c.indexOf(name) === 0) { - return c.substring(name.length, c.length); - } - } - return ''; - }, + // Definitions for RegExp + REG_PDEC: '(\\d+\\.\\d*)', + REG_FLOAT: '([-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?)', + + // Ajax call to server + requestURL: function (url, purpose, action, context, timeout) { + var httpRequest; + + if (window.XMLHttpRequest) { // Mozilla, Safari, ... + httpRequest = new XMLHttpRequest(); + } else if (window.ActiveXObject) { // IE + try { + httpRequest = new ActiveXObject('Msxml2.XMLHTTP'); + } + catch (e) { + try { + httpRequest = new ActiveXObject('Microsoft.XMLHTTP'); + } + catch (e) { } + } + } + if (!httpRequest) { + alert('Giving up: Cannot create an XMLHTTP instance for ' + purpose); + return false; + } + if (timeout) { + httpRequest.timeout = timeout * 1000; // seconds -> milliseconds + httpRequest.ontimeout = function () { + alert('Time out while ' + purpose); + }; + } + httpRequest.open('GET', url); + + // Send Credrentials + if ((context) && (context.options.credentials)) { + httpRequest.withCredentials = true; + + } + + // if request catalog need authenticate + if ((context) && (context.options.authenticate === 'csrftoken')) { + httpRequest.setRequestHeader('X-CSRFToken', this.getCookie('csrftoken')); + } + + if ((action)) { + httpRequest.onreadystatechange = function () { + action(context, httpRequest); + }; + } + httpRequest.send(); + }, + + // Return a dictionary of name/value pairs from a URL string, from + // http://stevenbenner.com/2010/03/javascript-regex-trick-parse-a-query-string-into-an-object/ + parseURL: function (url) { + var dict = {}; + url.replace( + new RegExp('([^?=&]+)(=([^&]*))?', 'g'), + function ($0, $1, $2, $3) { dict[$1] = $3; } + ); + return dict; + }, + + // Return a URL with an updated keyword/value queryString(from http://stackoverflow.com/a/5999118) + updateURL: function (url, keyword, value) { + var re = new RegExp('([?&])' + keyword + '=.*?(&|$)', 'i'), + separator = url.indexOf('?') !== -1 ? '&' : '?'; + + return url.match(re) ? url.replace(re, '$1' + keyword + '=' + value + '$2') : + url + separator + keyword + '=' + value; + }, + + // Return the domain of a given URL (from http://stackoverflow.com/a/28054735) + checkDomain: function (url) { + if (url.indexOf('//') === 0) { + url = location.protocol + url; + } + return url.toLowerCase().replace(/([a-z])?:\/\//, '$1').split('/')[0]; + }, + + // Check if a given URL is external (from http://stackoverflow.com/a/28054735) + isExternal: function (url) { + return ((url.indexOf(':') > -1 || url.indexOf('//') > -1) && + this.checkDomain(location.href) !== this.checkDomain(url)); + }, + + // Copy string to clipboard (from http://stackoverflow.com/a/33928558) + // Chrome 43+, Firefox 42+, Edge and Safari 10+ supported + // copyToClipboard: function (text) { + // if (document.queryCommandSupported && document.queryCommandSupported('copy')) { + // var textarea = document.createElement('textarea'); + // textarea.textContent = text; + // textarea.style.position = 'fixed'; // Prevent scrolling to bottom of page in MS Edge. + // document.body.appendChild(textarea); + // textarea.select(); + // try { + // return document.execCommand('copy'); // Security exception may be thrown by some browsers. + // } catch (ex) { + // console.warn('Copy to clipboard failed.', ex); + // return false; + // } finally { + // document.body.removeChild(textarea); + // } + // } + // }, + + // Add a short (<400ms) "flash" animation to an element + flashElement: function (elem) { + L.DomUtil.addClass(elem, 'leaflet-control-flash'); + setTimeout(function () { + L.DomUtil.removeClass(elem, 'leaflet-control-flash'); + }, 400); + + }, + + // Read content of a FITS header keyword + readFITSKey: function (keyword, str) { + var key = keyword.trim().toUpperCase().substr(0, 8), + nspace = 8 - key.length, + keyreg = new RegExp(key + (nspace > 0 ? '\\ {' + nspace.toString() + '}' : '') + + '=\\ *(?:\'((?:\\ *[^\'\\ ]+)*)\\ *\'|([-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?))'), + match = keyreg.exec(str); + if (!match) { + return null; + } else if (match[1]) { + return match[1]; + } else { + return match[2]; + } + }, + + // Return the distance between two world coords latLng1 and latLng2 in degrees + distance: function (latlng1, latlng2) { + var d2r = Math.PI / 180.0, + lat1 = latlng1.lat * d2r, + lat2 = latlng2.lat * d2r, + dLat = lat2 - lat1, + dLon = (latlng2.lng - latlng1.lng) * d2r, + sin1 = Math.sin(dLat / 2), + sin2 = Math.sin(dLon / 2); + + var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2); + + return Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) * 360.0 / Math.PI; + }, + + // Convert degrees to HMSDMS (DMS code from the Leaflet-Coordinates plug-in) + latLngToHMSDMS: function (latlng) { + var lng = (latlng.lng + 360.0) / 360.0; + lng = (lng - Math.floor(lng)) * 24.0; + var h = Math.floor(lng), + mf = (lng - h) * 60.0, + m = Math.floor(mf), + sf = (mf - m) * 60.0; + if (sf >= 60.0) { + m++; + sf = 0.0; + } + if (m === 60) { + h++; + m = 0; + } + var str = (h < 10 ? '0' : '') + h.toString() + ':' + (m < 10 ? '0' : '') + m.toString() + + ':' + (sf < 10.0 ? '0' : '') + sf.toFixed(3), + lat = Math.abs(latlng.lat), + sgn = latlng.lat < 0.0 ? '-' : '+', + d = Math.floor(lat); + mf = (lat - d) * 60.0; + m = Math.floor(mf); + sf = (mf - m) * 60.0; + if (sf >= 60.0) { + m++; + sf = 0.0; + } + if (m === 60) { + h++; + m = 0; + } + return str + ' ' + sgn + (d < 10 ? '0' : '') + d.toString() + ':' + + (m < 10 ? '0' : '') + m.toString() + ':' + + (sf < 10.0 ? '0' : '') + sf.toFixed(2); + }, + + // Convert HMSDMS to degrees + hmsDMSToLatLng: function (str) { + var result; + /* jshint ignore:start */ // Long regexp line (difficult to circumvent) + result = /^\s*(\d+)[h:](\d+)[m':](\d+\.?\d*)[s"]?\s*,?\s*([-+]?\d+)[d°:](\d+)[m':](\d+\.?\d*)[s"]?/g.exec(str); + /* jshint ignore:end */ + if (result && result.length >= 7) { + var dd = Number(result[4]); + + return L.latLng((dd < 0.0 ? -1.0 : 1.0) * + (Math.abs(dd) + Number(result[5]) / 60.0 + Number(result[6]) / 3600.0), + Number(result[1]) * 15.0 + Number(result[2]) / 4.0 + Number(result[3]) / 240.0); + } else { + return undefined; + } + }, + + + // returns the value of a specified cookie (from http://www.w3schools.com/js/js_cookies.asp) + getCookie: function (cname) { + var name = cname + '='; + var ca = document.cookie.split(';'); + for (var i = 0; i < ca.length; i++) { + var c = ca[i]; + while (c.charAt(0) === ' ') { + c = c.substring(1); + } + if (c.indexOf(name) === 0) { + return c.substring(name.length, c.length); + } + } + return ''; + } + }; + + /* # L.TileLayer.IIP adds support for IIP layers to Leaflet # (see http://iipimage.sourceforge.net/documentation/protocol/) @@ -1155,33 +973,33 @@ L.IIPUtils = { */ L.TileLayer.IIP = L.TileLayer.extend({ - options: { - title: '', - crs: null, - nativeCelsys: false, - center: false, - fov: false, - minZoom: 0, - maxZoom: null, - maxNativeZoom: 18, - noWrap: true, - contrast: 1.0, - colorSat: 1.0, - gamma: 1.0, - cMap: 'grey', - invertCMap: false, - quality: 90, - mixingMode: 'color', - channelColors: [], - channelLabels: [], - channelLabelMatch: '.*', - channelUnits: [], - minMaxValues: [], - defaultChannel: 0, - credentials: false, - sesameURL: 'https://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame', - - /* + options: { + title: '', + crs: null, + nativeCelsys: false, + center: false, + fov: false, + minZoom: 0, + maxZoom: null, + maxNativeZoom: 18, + noWrap: true, + contrast: 1.0, + colorSat: 1.0, + gamma: 1.0, + cMap: 'grey', + invertCMap: false, + quality: 90, + mixingMode: 'color', + channelColors: [], + channelLabels: [], + channelLabelMatch: '.*', + channelUnits: [], + minMaxValues: [], + defaultChannel: 0, + credentials: false, + sesameURL: 'https://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame' + + /* pane: 'tilePane', opacity: 1, attribution: , @@ -1195,647 +1013,576 @@ L.TileLayer.IIP = L.TileLayer.extend({ zoomReverse: , detectRetina: , */ - }, - - // Default IIPImage rendering parameters - iipdefault: { - contrast: 1, - gamma: 1, - cMap: 'grey', - invertCMap: false, - minValue: [], - maxValue: [], - channelColors: [ - [''], - ['#FFFFFF'], - ['#00BAFF', '#FFBA00'], - ['#0000FF', '#00FF00', '#FF0000'], - ['#0000E0', '#00BA88', '#88BA00', '#E00000'], - ['#0000CA', '#007BA8', '#00CA00', '#A87B00', '#CA0000'], - ['#0000BA', '#00719B', '#009B71', '#719B00', '#9B7100', '#BA0000'], - ], - quality: 90, - }, - - initialize: function (url, options) { - this.type = 'tilelayer'; - this._url = url.replace(/\&.*$/g, ''); - - options = L.setOptions(this, options); - - // detecting retina displays, adjusting tileSize and zoom levels - if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) { - options.tileSize = Math.floor(options.tileSize / 2); - options.zoomOffset++; - - options.minZoom = Math.max(0, options.minZoom); - options.maxZoom--; - } - - if (typeof options.subdomains === 'string') { - options.subdomains = options.subdomains.split(''); - } - - this.iipTileSize = { x: 256, y: 256 }; - this.iipImageSize = []; - this.iipImageSize[0] = this.iipTileSize; - this.iipGridSize = []; - this.iipGridSize[0] = { x: 1, y: 1 }; - this.iipBPP = 8; - this.iipMode = options.mixingMode; // Image rendering mode: 'mono' or 'color' - this.iipChannel = 0; - this.iipNChannel = 1; - this.iipMinZoom = options.minZoom; - this.iipMaxZoom = options.maxZoom; - this.iipContrast = options.contrast; - this.iipColorSat = options.colorSat; - this.iipGamma = options.gamma; - this.iipCMap = options.cMap; - this.iipInvertCMap = options.invertCMap; - this.iipMinValue = []; - this.iipMinValue[0] = 0.0; - this.iipMaxValue = []; - this.iipMaxValue[0] = 255.0; - this.iipMix = [[]]; - this.iipRGB = []; - this.iipChannelLabels = []; - this.iipChannelFlags = []; - this.iipChannelUnits = []; - this.iipQuality = options.quality; - - this._title = - options.title.length > 0 - ? options.title - : this._url.match(/^.*\/(.*)\..*$/)[1]; - this.getIIPMetaData(this._url); - - // for https://github.com/Leaflet/Leaflet/issues/137 - if (!L.Browser.android) { - this.on('tileunload', this._onTileRemove); - } - return this; - }, - - getIIPMetaData: function (url) { - L.IIPUtils.requestURL( - url + - '&obj=IIP,1.0&obj=max-size&obj=tile-size' + - '&obj=resolution-number&obj=bits-per-channel' + - '&obj=min-max-sample-values&obj=subject', - 'getting IIP metadata', - this._parseMetadata, - this - ); - }, - - _parseMetadata: function (layer, httpRequest) { - if (httpRequest.readyState === 4) { - if (httpRequest.status === 200) { - var response = httpRequest.responseText, - matches = layer._readIIPKey(response, 'IIP', L.IIPUtils.REG_PDEC); - if (!matches) { - alert( - 'Error: Unexpected response from IIP server ' + - layer._url.replace(/\?.*$/g, '') - ); - } - var options = layer.options, - iipdefault = layer.iipdefault; - - matches = layer._readIIPKey(response, 'Max-size', '(\\d+)\\s+(\\d+)'); - var maxsize = { - x: parseInt(matches[1], 10), - y: parseInt(matches[2], 10), - }; - matches = layer._readIIPKey(response, 'Tile-size', '(\\d+)\\s+(\\d+)'); - layer.iipTileSize = { - x: parseInt(matches[1], 10), - y: parseInt(matches[2], 10), - }; - - options.tileSize = layer.iipTileSize.x; - - // Find the lowest and highest zoom levels - matches = layer._readIIPKey(response, 'Resolution-number', '(\\d+)'); - layer.iipMaxZoom = parseInt(matches[1], 10) - 1; - if (layer.iipMinZoom > options.minZoom) { - options.minZoom = layer.iipMinZoom; - } - if (!options.maxZoom) { - options.maxZoom = layer.iipMaxZoom + 6; - } - options.maxNativeZoom = layer.iipMaxZoom; - - // Set grid sizes - for (var z = 0; z <= layer.iipMaxZoom; z++) { - layer.iipImageSize[z] = { - x: Math.floor(maxsize.x / Math.pow(2, layer.iipMaxZoom - z)), - y: Math.floor(maxsize.y / Math.pow(2, layer.iipMaxZoom - z)), - }; - layer.iipGridSize[z] = { - x: Math.ceil(layer.iipImageSize[z].x / layer.iipTileSize.x), - y: Math.ceil(layer.iipImageSize[z].y / layer.iipTileSize.y), - }; - } - // (Virtual) grid sizes for extra zooming - for (z = layer.iipMaxZoom; z <= options.maxZoom; z++) { - layer.iipGridSize[z] = layer.iipGridSize[layer.iipMaxZoom]; - } - - // Set pixel bpp - matches = layer._readIIPKey(response, 'Bits-per-channel', '(\\d+)'); - layer.iipBPP = parseInt(matches[1], 10); - // Only 32bit data are likely to be linearly quantized - if (layer.iipGamma === layer.iipdefault.gamma) { - layer.iipGamma = layer.iipBPP >= 32 ? 2.2 : 1.0; - } - - // Pre-computed min and max pixel values, as well as number of channels - matches = layer._readIIPKey( - response, - 'Min-Max-sample-values', - '\\s*(.*)' - ); - var str = matches[1].split(/\s+/), - nchannel = (layer.iipNChannel = str.length / 2), - mmc = 0; - for (var c = 0; c < nchannel; c++) { - iipdefault.minValue[c] = parseFloat(str[mmc++]); - iipdefault.maxValue[c] = parseFloat(str[mmc++]); - } - - // Override min and max pixel values based on user provided options - var minmax = options.minMaxValues; - if (minmax.length) { - for (c = 0; c < nchannel; c++) { - if (minmax[c] !== undefined && minmax[c].length) { - layer.iipMinValue[c] = minmax[c][0]; - layer.iipMaxValue[c] = minmax[c][1]; - } else { - layer.iipMinValue[c] = iipdefault.minValue[c]; - layer.iipMaxValue[c] = iipdefault.maxValue[c]; - } - } - } else { - for (c = 0; c < nchannel; c++) { - layer.iipMinValue[c] = iipdefault.minValue[c]; - layer.iipMaxValue[c] = iipdefault.maxValue[c]; - } - } - - // Default channel - layer.iipChannel = options.defaultChannel; - - // Channel labels - var inlabels = options.channelLabels, - ninlabel = inlabels.length, - labels = layer.iipChannelLabels, - inunits = options.channelUnits, - ninunits = inunits.length, - units = layer.iipChannelUnits, - key = L.IIPUtils.readFITSKey, - numstr, - value; - - for (c = 0; c < nchannel; c++) { - if (c < ninlabel) { - labels[c] = inlabels[c]; - } else { - numstr = (c + 1).toString(); - value = key( - 'CHAN' + - (c < 9 ? '000' : c < 99 ? '00' : c < 999 ? '0' : '') + - numstr, - response - ); - labels[c] = value ? value : 'Channel #' + numstr; - } - } - - // Copy those units that have been provided - for (c = 0; c < ninunits; c++) { - units[c] = inunits[c]; - } - // Fill out units that are not provided with a default string - for (c = ninunits; c < nchannel; c++) { - units[c] = 'ADUs'; - } - - // Initialize mixing matrix depending on arguments and the number of channels - var cc = 0, - mix = layer.iipMix, - omix = options.channelColors, - rgb = layer.iipRGB, - re = new RegExp(options.channelLabelMatch), - nchanon = 0, - channelflags = layer.iipChannelFlags; - - nchanon = 0; - for (c = 0; c < nchannel; c++) { - channelflags[c] = re.test(labels[c]); - if (channelflags[c]) { - nchanon++; - } - } - if (nchanon >= iipdefault.channelColors.length) { - nchanon = iipdefault.channelColors.length - 1; - } - - for (c = 0; c < nchannel; c++) { - mix[c] = []; - var col = 3; - if (omix.length && omix[c] && omix[c].length === 3) { - // Copy RGB triplet - rgb[c] = L.rgb(omix[c][0], omix[c][1], omix[c][2]); - } else { - rgb[c] = L.rgb(0.0, 0.0, 0.0); - } - if (omix.length === 0 && channelflags[c] && cc < nchanon) { - rgb[c] = L.rgb(iipdefault.channelColors[nchanon][cc++]); - } - // Compute the current row of the mixing matrix - layer.rgbToMix(c); - } - - if (options.bounds) { - options.bounds = L.latLngBounds(options.bounds); - } - layer.wcs = options.crs - ? options.crs - : new L.CRS.WCS(response, { - nativeCelsys: layer.options.nativeCelsys, - nzoom: layer.iipMaxZoom + 1, - tileSize: layer.iipTileSize, - }); - layer.iipMetaReady = true; - layer.fire('metaload'); - } else { - alert('There was a problem with the IIP metadata request.'); - } - } - }, - - // Convert an RGB colour and saturation settings to mixing matrix elements - rgbToMix: function (chan, rgb) { - if (rgb) { - this.iipRGB[chan] = rgb.clone(); - } else { - rgb = this.iipRGB[chan]; - } - - var cr = this._gammaCorr(rgb.r), - cg = this._gammaCorr(rgb.g), - cb = this._gammaCorr(rgb.b), - lum = (cr + cg + cb) / 3.0, - alpha = this.iipColorSat / 3.0; - - this.iipMix[chan][0] = lum + alpha * (2.0 * cr - cg - cb); - this.iipMix[chan][1] = lum + alpha * (2.0 * cg - cr - cb); - this.iipMix[chan][2] = lum + alpha * (2.0 * cb - cr - cg); - - return; - }, - - // Current channel index defines mixing matrix elements in "mono" mode - updateMono: function () { - this.iipMode = 'mono'; - }, - - // RGB colours and saturation settings define mixing matrix elements in "color" mode - updateMix: function () { - var nchannel = this.iipNChannel; - - this.iipMode = 'color'; - for (var c = 0; c < nchannel; c++) { - this.rgbToMix(c, this.iipRGB[c]); - } - }, - - // Apply gamma correction - _gammaCorr: function (val) { - return val > 0.0 ? Math.pow(val, this.iipGamma) : 0.0; - }, - - _readIIPKey: function (str, keyword, regexp) { - var reg = new RegExp(keyword + ':' + regexp); - return reg.exec(str); - }, - - addTo: function (map) { - if (this.iipMetaReady) { - // IIP data are ready so we can go - this._addToMap(map); - } else { - // Wait for metadata request to complete - this.once( - 'metaload', - function () { - this._addToMap(map); - }, - this - ); - } - return this; - }, - - _addToMap: function (map) { - var zoom, - newcrs = this.wcs, - curcrs = map.options.crs, - prevcrs = map._prevcrs, - maploadedflag = map._loaded, - center; - - if (maploadedflag) { - curcrs._prevLatLng = map.getCenter(); - curcrs._prevZoom = map.getZoom(); - } - - map._prevcrs = map.options.crs = newcrs; - L.TileLayer.prototype.addTo.call(this, map); - - // Go to previous layers' coordinates if applicable - if ( - prevcrs && - newcrs !== curcrs && - maploadedflag && - newcrs.pixelFlag === curcrs.pixelFlag - ) { - center = curcrs._prevLatLng; - zoom = curcrs._prevZoom; - var prevpixscale = prevcrs.pixelScale(zoom, center), - newpixscale = newcrs.pixelScale(zoom, center); - if (prevpixscale > 1e-20 && newpixscale > 1e-20) { - zoom += Math.round(Math.LOG2E * Math.log(newpixscale / prevpixscale)); - } - // Else go back to previous recorded position for the new layer - } else if (newcrs._prevLatLng) { - center = newcrs._prevLatLng; - zoom = newcrs._prevZoom; - } else if (this.options.center) { - // Default center coordinates and zoom - var latlng = - typeof this.options.center === 'string' - ? newcrs.parseCoords(decodeURI(this.options.center)) - : this.options.center; - if (latlng) { - if (this.options.fov) { - zoom = newcrs.fovToZoom(map, this.options.fov, latlng); - } - map.setView(latlng, zoom, { reset: true, animate: false }); - } else { - // If not, ask Sesame@CDS! - L.IIPUtils.requestURL( - this.options.sesameURL + '/-oI/A?' + this.options.center, - 'getting coordinates for ' + this.options.center, - function (_this, httpRequest) { - if (httpRequest.readyState === 4) { - if (httpRequest.status === 200) { - var str = httpRequest.responseText, - latlng = newcrs.parseCoords(str); - if (latlng) { - if (_this.options.fov) { - zoom = newcrs.fovToZoom(map, _this.options.fov, latlng); - } - map.setView(latlng, zoom, { reset: true, animate: false }); - } else { - map.setView(newcrs.projparam.crval, zoom, { - reset: true, - animate: false, - }); - alert(str + ': Unknown location'); - } - } else { - map.setView(newcrs.projparam.crval, zoom, { - reset: true, - animate: false, - }); - alert( - 'There was a problem with the request to the Sesame service at CDS' - ); - } - } - }, - this, - 10 - ); - } - } else { - map.setView(newcrs.projparam.crval, zoom, { - reset: true, - animate: false, - }); - } - }, - - _getTileSizeFac: function () { - var map = this._map, - zoom = this._tileZoom, - zoomN = this.options.maxNativeZoom; - return zoomN && zoom > zoomN - ? Math.round(map.getZoomScale(zoom) / map.getZoomScale(zoomN)) - : 1; - }, - - _isValidTile: function (coords) { - var crs = this._map.options.crs; - - if (!crs.infinite) { - // don't load tile if it's out of bounds and not wrapped - var bounds = this._globalTileRange; - if ( - (!crs.wrapLng && - (coords.x < bounds.min.x || coords.x > bounds.max.x)) || - (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y)) - ) { - return false; - } - } - - // don't load tile if it's out of the tile grid - var z = this._getZoomForUrl(), - wcoords = coords.clone(); - this._wrapCoords(wcoords); - if ( - wcoords.x < 0 || - wcoords.x >= this.iipGridSize[z].x || - wcoords.y < 0 || - wcoords.y >= this.iipGridSize[z].y - ) { - return false; - } - - if (!this.options.bounds) { - return true; - } - - // don't load tile if it doesn't intersect the bounds in options - var tileBounds = this._tileCoordsToBounds(coords); - return L.latLngBounds(this.options.bounds).intersects(tileBounds); - }, - - createTile: function (coords, done) { - var tile = L.TileLayer.prototype.createTile.call(this, coords, done); - - tile.coords = coords; - - return tile; - }, - - getTileUrl: function (coords) { - var str = this._url, - z = this._getZoomForUrl(); - - if (this.iipCMap !== this.iipdefault.cMap) { - str += '&CMP=' + this.iipCMap; - } - if (this.iipInvertCMap !== this.iipdefault.invertCMap) { - str += '&INV'; - } - if (this.iipContrast !== this.iipdefault.contrast) { - str += '&CNT=' + this.iipContrast.toString(); - } - if (this.iipGamma !== this.iipdefault.gamma) { - str += '&GAM=' + (1.0 / this.iipGamma).toFixed(4); - } - for (var c = 0; c < this.iipNChannel; c++) { - if ( - this.iipMinValue[c] !== this.iipdefault.minValue[c] || - this.iipMaxValue[c] !== this.iipdefault.maxValue[c] - ) { - str += - '&MINMAX=' + - (c + 1).toString() + - ':' + - this.iipMinValue[c].toString() + - ',' + - this.iipMaxValue[c].toString(); - } - } - - var nchannel = this.iipNChannel, - mix = this.iipMix, - m, - n; - - str += '&CTW='; - - if (this.iipMode === 'color') { - for (n = 0; n < 3; n++) { - if (n) { - str += ';'; - } - str += mix[0][n].toString(); - for (m = 1; m < nchannel; m++) { - if (mix[m][n] !== undefined) { - str += ',' + mix[m][n].toString(); - } - } - } - } else { - var cc = this.iipChannel; - - if (cc >= nchannel) { - cc = 0; - } - if (cc < nchannel) { - nchannel = cc + 1; - } - for (n = 0; n < 3; n++) { - if (n) { - str += ';'; - } - str += cc === 0 ? '1' : '0'; - for (m = 1; m < nchannel; m++) { - str += ',' + (cc === m ? '1' : '0'); - } - } - } - - if (this.iipQuality !== this.iipdefault.quality) { - str += '&QLT=' + this.iipQuality.toString(); - } - return ( - str + - '&JTL=' + - z.toString() + - ',' + - (coords.x + this.iipGridSize[z].x * coords.y).toString() - ); - }, - - _initTile: function (tile) { - L.DomUtil.addClass(tile, 'leaflet-tile'); - var tileSizeFac = this._getTileSizeFac(); - - // Force pixels to be visible at high zoom factos whenever possible - if (tileSizeFac > 1) { - if (L.Browser.ie) { - tile.style.msInterpolationMode = 'nearest-neighbor'; - } else if (L.Browser.chrome) { - tile.style.imageRendering = 'pixelated'; - } else if (L.Browser.gecko) { - tile.style.imageRendering = '-moz-crisp-edges'; - } else { - tile.style.imageRendering = '-webkit-optimize-contrast'; - } - } - - // Compute tile size (IIP tile size can be less at image borders) - var coords = tile.coords, - z = this._getZoomForUrl(); - - if (z > this.iipMaxZoom) { - z = this.iipMaxZoom; - } - var sizeX = - coords.x + 1 === this.iipGridSize[z].x - ? this.iipImageSize[z].x % this.iipTileSize.x - : this.iipTileSize.x, - sizeY = - coords.y + 1 === this.iipGridSize[z].y - ? this.iipImageSize[z].y % this.iipTileSize.y - : this.iipTileSize.y; - - if (sizeX === 0) { - sizeX = this.iipTileSize.x; - } - if (sizeY === 0) { - sizeY = this.iipTileSize.y; - } - - sizeX *= tileSizeFac; - sizeY *= tileSizeFac; - /* + }, + + // Default IIPImage rendering parameters + iipdefault: { + contrast: 1, + gamma: 1, + cMap: 'grey', + invertCMap: false, + minValue: [], + maxValue: [], + channelColors: [ + [''], + ['#FFFFFF'], + ['#00BAFF', '#FFBA00'], + ['#0000FF', '#00FF00', '#FF0000'], + ['#0000E0', '#00BA88', '#88BA00', '#E00000'], + ['#0000CA', '#007BA8', '#00CA00', '#A87B00', '#CA0000'], + ['#0000BA', '#00719B', '#009B71', '#719B00', '#9B7100', '#BA0000'] + ], + quality: 90 + }, + + initialize: function (url, options) { + this.type = 'tilelayer'; + this._url = url.replace(/\&.*$/g, ''); + + options = L.setOptions(this, options); + + // detecting retina displays, adjusting tileSize and zoom levels + if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) { + + options.tileSize = Math.floor(options.tileSize / 2); + options.zoomOffset++; + + options.minZoom = Math.max(0, options.minZoom); + options.maxZoom--; + } + + if (typeof options.subdomains === 'string') { + options.subdomains = options.subdomains.split(''); + } + + this.iipTileSize = { x: 256, y: 256 }; + this.iipImageSize = []; + this.iipImageSize[0] = this.iipTileSize; + this.iipGridSize = []; + this.iipGridSize[0] = { x: 1, y: 1 }; + this.iipBPP = 8; + this.iipMode = options.mixingMode; // Image rendering mode: 'mono' or 'color' + this.iipChannel = 0; + this.iipNChannel = 1; + this.iipMinZoom = options.minZoom; + this.iipMaxZoom = options.maxZoom; + this.iipContrast = options.contrast; + this.iipColorSat = options.colorSat; + this.iipGamma = options.gamma; + this.iipCMap = options.cMap; + this.iipInvertCMap = options.invertCMap; + this.iipMinValue = []; + this.iipMinValue[0] = 0.0; + this.iipMaxValue = []; + this.iipMaxValue[0] = 255.0; + this.iipMix = [[]]; + this.iipRGB = []; + this.iipChannelLabels = []; + this.iipChannelFlags = []; + this.iipChannelUnits = []; + this.iipQuality = options.quality; + + this._title = options.title.length > 0 ? options.title : + this._url.match(/^.*\/(.*)\..*$/)[1]; + this.getIIPMetaData(this._url); + + // for https://github.com/Leaflet/Leaflet/issues/137 + if (!L.Browser.android) { + this.on('tileunload', this._onTileRemove); + } + return this; + }, + + getIIPMetaData: function (url) { + L.IIPUtils.requestURL(url + + '&obj=IIP,1.0&obj=max-size&obj=tile-size' + + '&obj=resolution-number&obj=bits-per-channel' + + '&obj=min-max-sample-values&obj=subject', + 'getting IIP metadata', + this._parseMetadata, this); + }, + + _parseMetadata: function (layer, httpRequest) { + if (httpRequest.readyState === 4) { + if (httpRequest.status === 200) { + var response = httpRequest.responseText, + matches = layer._readIIPKey(response, 'IIP', L.IIPUtils.REG_PDEC); + if (!matches) { + alert('Error: Unexpected response from IIP server ' + + layer._url.replace(/\?.*$/g, '')); + } + var options = layer.options, + iipdefault = layer.iipdefault; + + matches = layer._readIIPKey(response, 'Max-size', '(\\d+)\\s+(\\d+)'); + var maxsize = { + x: parseInt(matches[1], 10), + y: parseInt(matches[2], 10) + }; + matches = layer._readIIPKey(response, 'Tile-size', '(\\d+)\\s+(\\d+)'); + layer.iipTileSize = { + x: parseInt(matches[1], 10), + y: parseInt(matches[2], 10) + }; + + options.tileSize = layer.iipTileSize.x; + + // Find the lowest and highest zoom levels + matches = layer._readIIPKey(response, 'Resolution-number', '(\\d+)'); + layer.iipMaxZoom = parseInt(matches[1], 10) - 1; + if (layer.iipMinZoom > options.minZoom) { + options.minZoom = layer.iipMinZoom; + } + if (!options.maxZoom) { + options.maxZoom = layer.iipMaxZoom + 6; + } + options.maxNativeZoom = layer.iipMaxZoom; + + // Set grid sizes + for (var z = 0; z <= layer.iipMaxZoom; z++) { + layer.iipImageSize[z] = { + x: Math.floor(maxsize.x / Math.pow(2, layer.iipMaxZoom - z)), + y: Math.floor(maxsize.y / Math.pow(2, layer.iipMaxZoom - z)) + }; + layer.iipGridSize[z] = { + x: Math.ceil(layer.iipImageSize[z].x / layer.iipTileSize.x), + y: Math.ceil(layer.iipImageSize[z].y / layer.iipTileSize.y) + }; + } + // (Virtual) grid sizes for extra zooming + for (z = layer.iipMaxZoom; z <= options.maxZoom; z++) { + layer.iipGridSize[z] = layer.iipGridSize[layer.iipMaxZoom]; + } + + // Set pixel bpp + matches = layer._readIIPKey(response, 'Bits-per-channel', '(\\d+)'); + layer.iipBPP = parseInt(matches[1], 10); + // Only 32bit data are likely to be linearly quantized + if (layer.iipGamma === layer.iipdefault.gamma) { + layer.iipGamma = layer.iipBPP >= 32 ? 2.2 : 1.0; + } + + // Pre-computed min and max pixel values, as well as number of channels + matches = layer._readIIPKey(response, 'Min-Max-sample-values', + '\\s*(.*)'); + var str = matches[1].split(/\s+/), + nchannel = layer.iipNChannel = (str.length / 2), + mmc = 0; + for (var c = 0; c < nchannel; c++) { + iipdefault.minValue[c] = parseFloat(str[mmc++]); + iipdefault.maxValue[c] = parseFloat(str[mmc++]); + } + + // Override min and max pixel values based on user provided options + var minmax = options.minMaxValues; + if (minmax.length) { + for (c = 0; c < nchannel; c++) { + if (minmax[c] !== undefined && minmax[c].length) { + layer.iipMinValue[c] = minmax[c][0]; + layer.iipMaxValue[c] = minmax[c][1]; + } else { + layer.iipMinValue[c] = iipdefault.minValue[c]; + layer.iipMaxValue[c] = iipdefault.maxValue[c]; + } + } + } else { + for (c = 0; c < nchannel; c++) { + layer.iipMinValue[c] = iipdefault.minValue[c]; + layer.iipMaxValue[c] = iipdefault.maxValue[c]; + } + } + + // Default channel + layer.iipChannel = options.defaultChannel; + + // Channel labels + var inlabels = options.channelLabels, + ninlabel = inlabels.length, + labels = layer.iipChannelLabels, + inunits = options.channelUnits, + ninunits = inunits.length, + units = layer.iipChannelUnits, + key = L.IIPUtils.readFITSKey, + numstr, value; + + for (c = 0; c < nchannel; c++) { + if (c < ninlabel) { + labels[c] = inlabels[c]; + } else { + numstr = (c + 1).toString(); + value = key('CHAN' + + (c < 9 ? '000' : (c < 99 ? '00' : (c < 999 ? '0' : ''))) + numstr, + response); + labels[c] = value ? value : 'Channel #' + numstr; + } + } + + // Copy those units that have been provided + for (c = 0; c < ninunits; c++) { + units[c] = inunits[c]; + } + // Fill out units that are not provided with a default string + for (c = ninunits; c < nchannel; c++) { + units[c] = 'ADUs'; + } + + // Initialize mixing matrix depending on arguments and the number of channels + var cc = 0, + mix = layer.iipMix, + omix = options.channelColors, + rgb = layer.iipRGB, + re = new RegExp(options.channelLabelMatch), + nchanon = 0, + channelflags = layer.iipChannelFlags; + + nchanon = 0; + for (c = 0; c < nchannel; c++) { + channelflags[c] = re.test(labels[c]); + if (channelflags[c]) { + nchanon++; + } + } + if (nchanon >= iipdefault.channelColors.length) { + nchanon = iipdefault.channelColors.length - 1; + } + + for (c = 0; c < nchannel; c++) { + mix[c] = []; + var col = 3; + if (omix.length && omix[c] && omix[c].length === 3) { + // Copy RGB triplet + rgb[c] = L.rgb(omix[c][0], omix[c][1], omix[c][2]); + } else { + rgb[c] = L.rgb(0.0, 0.0, 0.0); + } + if (omix.length === 0 && channelflags[c] && cc < nchanon) { + rgb[c] = L.rgb(iipdefault.channelColors[nchanon][cc++]); + } + // Compute the current row of the mixing matrix + layer.rgbToMix(c); + } + + if (options.bounds) { + options.bounds = L.latLngBounds(options.bounds); + } + layer.wcs = options.crs ? options.crs : new L.CRS.WCS(response, { + nativeCelsys: layer.options.nativeCelsys, + nzoom: layer.iipMaxZoom + 1, + tileSize: layer.iipTileSize + }); + layer.iipMetaReady = true; + layer.fire('metaload'); + } else { + alert('There was a problem with the IIP metadata request.'); + } + } + }, + + // Convert an RGB colour and saturation settings to mixing matrix elements + rgbToMix: function (chan, rgb) { + if (rgb) { + this.iipRGB[chan] = rgb.clone(); + } else { + rgb = this.iipRGB[chan]; + } + + var cr = this._gammaCorr(rgb.r), + cg = this._gammaCorr(rgb.g), + cb = this._gammaCorr(rgb.b), + lum = (cr + cg + cb) / 3.0, + alpha = this.iipColorSat / 3.0; + + this.iipMix[chan][0] = lum + alpha * (2.0 * cr - cg - cb); + this.iipMix[chan][1] = lum + alpha * (2.0 * cg - cr - cb); + this.iipMix[chan][2] = lum + alpha * (2.0 * cb - cr - cg); + + return; + }, + + // Current channel index defines mixing matrix elements in "mono" mode + updateMono: function () { + this.iipMode = 'mono'; + }, + + // RGB colours and saturation settings define mixing matrix elements in "color" mode + updateMix: function () { + var nchannel = this.iipNChannel; + + this.iipMode = 'color'; + for (var c = 0; c < nchannel; c++) { + this.rgbToMix(c, this.iipRGB[c]); + } + }, + + // Apply gamma correction + _gammaCorr: function (val) { + return val > 0.0 ? Math.pow(val, this.iipGamma) : 0.0; + }, + + _readIIPKey: function (str, keyword, regexp) { + var reg = new RegExp(keyword + ':' + regexp); + return reg.exec(str); + }, + + addTo: function (map) { + if (this.iipMetaReady) { + // IIP data are ready so we can go + this._addToMap(map); + } + else { + // Wait for metadata request to complete + this.once('metaload', function () { + this._addToMap(map); + }, this); + } + return this; + }, + + _addToMap: function (map) { + var zoom, + newcrs = this.wcs, + curcrs = map.options.crs, + prevcrs = map._prevcrs, + maploadedflag = map._loaded, + center; + + if (maploadedflag) { + curcrs._prevLatLng = map.getCenter(); + curcrs._prevZoom = map.getZoom(); + } + + map._prevcrs = map.options.crs = newcrs; + L.TileLayer.prototype.addTo.call(this, map); + + // Go to previous layers' coordinates if applicable + if (prevcrs && newcrs !== curcrs && maploadedflag && + newcrs.pixelFlag === curcrs.pixelFlag) { + center = curcrs._prevLatLng; + zoom = curcrs._prevZoom; + var prevpixscale = prevcrs.pixelScale(zoom, center), + newpixscale = newcrs.pixelScale(zoom, center); + if (prevpixscale > 1e-20 && newpixscale > 1e-20) { + zoom += Math.round(Math.LOG2E * + Math.log(newpixscale / prevpixscale)); + } + // Else go back to previous recorded position for the new layer + } else if (newcrs._prevLatLng) { + center = newcrs._prevLatLng; + zoom = newcrs._prevZoom; + } else if (this.options.center) { + // Default center coordinates and zoom + var latlng = (typeof this.options.center === 'string') ? + newcrs.parseCoords(decodeURI(this.options.center)) : + this.options.center; + if (latlng) { + if (this.options.fov) { + zoom = newcrs.fovToZoom(map, this.options.fov, latlng); + } + map.setView(latlng, zoom, { reset: true, animate: false }); + } else { + // If not, ask Sesame@CDS! + L.IIPUtils.requestURL( + this.options.sesameURL + '/-oI/A?' + + this.options.center, + 'getting coordinates for ' + this.options.center, + function (_this, httpRequest) { + if (httpRequest.readyState === 4) { + if (httpRequest.status === 200) { + var str = httpRequest.responseText, + latlng = newcrs.parseCoords(str); + if (latlng) { + if (_this.options.fov) { + zoom = newcrs.fovToZoom(map, _this.options.fov, latlng); + } + map.setView(latlng, zoom, { reset: true, animate: false }); + } else { + map.setView(newcrs.projparam.crval, zoom, { reset: true, animate: false }); + alert(str + ': Unknown location'); + } + } else { + map.setView(newcrs.projparam.crval, zoom, { reset: true, animate: false }); + alert('There was a problem with the request to the Sesame service at CDS'); + } + } + }, this, 10 + ); + } + } else { + map.setView(newcrs.projparam.crval, zoom, { reset: true, animate: false }); + } + }, + + _getTileSizeFac: function () { + var map = this._map, + zoom = this._tileZoom, + zoomN = this.options.maxNativeZoom; + return (zoomN && zoom > zoomN) ? + Math.round(map.getZoomScale(zoom) / map.getZoomScale(zoomN)) : 1; + }, + + _isValidTile: function (coords) { + var crs = this._map.options.crs; + + if (!crs.infinite) { + // don't load tile if it's out of bounds and not wrapped + var bounds = this._globalTileRange; + if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) || + (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; } + } + + // don't load tile if it's out of the tile grid + var z = this._getZoomForUrl(), + wcoords = coords.clone(); + this._wrapCoords(wcoords); + if (wcoords.x < 0 || wcoords.x >= this.iipGridSize[z].x || + wcoords.y < 0 || wcoords.y >= this.iipGridSize[z].y) { + return false; + } + + if (!this.options.bounds) { return true; } + + // don't load tile if it doesn't intersect the bounds in options + var tileBounds = this._tileCoordsToBounds(coords); + return L.latLngBounds(this.options.bounds).intersects(tileBounds); + }, + + createTile: function (coords, done) { + var tile = L.TileLayer.prototype.createTile.call(this, coords, done); + + tile.coords = coords; + + return tile; + }, + + getTileUrl: function (coords) { + var str = this._url, + z = this._getZoomForUrl(); + + if (this.iipCMap !== this.iipdefault.cMap) { + str += '&CMP=' + this.iipCMap; + } + if (this.iipInvertCMap !== this.iipdefault.invertCMap) { + str += '&INV'; + } + if (this.iipContrast !== this.iipdefault.contrast) { + str += '&CNT=' + this.iipContrast.toString(); + } + if (this.iipGamma !== this.iipdefault.gamma) { + str += '&GAM=' + (1.0 / this.iipGamma).toFixed(4); + } + for (var c = 0; c < this.iipNChannel; c++) { + if (this.iipMinValue[c] !== this.iipdefault.minValue[c] || + this.iipMaxValue[c] !== this.iipdefault.maxValue[c]) { + str += '&MINMAX=' + (c + 1).toString() + ':' + + this.iipMinValue[c].toString() + ',' + this.iipMaxValue[c].toString(); + } + } + + var nchannel = this.iipNChannel, + mix = this.iipMix, + m, n; + + str += '&CTW='; + + if (this.iipMode === 'color') { + for (n = 0; n < 3; n++) { + if (n) { str += ';'; } + str += mix[0][n].toString(); + for (m = 1; m < nchannel; m++) { + if (mix[m][n] !== undefined) { + str += ',' + mix[m][n].toString(); + } + } + } + } else { + var cc = this.iipChannel; + + if (cc >= nchannel) { cc = 0; } + if (cc < nchannel) { nchannel = cc + 1; } + for (n = 0; n < 3; n++) { + if (n) { str += ';'; } + str += (cc === 0 ? '1' : '0'); + for (m = 1; m < nchannel; m++) { + str += ',' + (cc === m ? '1' : '0'); + } + } + } + + if (this.iipQuality !== this.iipdefault.quality) { + str += '&QLT=' + this.iipQuality.toString(); + } + return str + '&JTL=' + z.toString() + ',' + + (coords.x + this.iipGridSize[z].x * coords.y).toString(); + }, + + _initTile: function (tile) { + L.DomUtil.addClass(tile, 'leaflet-tile'); + var tileSizeFac = this._getTileSizeFac(); + + // Force pixels to be visible at high zoom factos whenever possible + if (tileSizeFac > 1) { + if (L.Browser.ie) { + tile.style.msInterpolationMode = 'nearest-neighbor'; + } else if (L.Browser.chrome) { + tile.style.imageRendering = 'pixelated'; + } else if (L.Browser.gecko) { + tile.style.imageRendering = '-moz-crisp-edges'; + } else { + tile.style.imageRendering = '-webkit-optimize-contrast'; + } + } + + // Compute tile size (IIP tile size can be less at image borders) + var coords = tile.coords, + z = this._getZoomForUrl(); + + if (z > this.iipMaxZoom) { z = this.iipMaxZoom; } + var sizeX = coords.x + 1 === this.iipGridSize[z].x ? + this.iipImageSize[z].x % this.iipTileSize.x : this.iipTileSize.x, + sizeY = coords.y + 1 === this.iipGridSize[z].y ? + this.iipImageSize[z].y % this.iipTileSize.y : this.iipTileSize.y; + + if (sizeX === 0) { + sizeX = this.iipTileSize.x; + } + if (sizeY === 0) { + sizeY = this.iipTileSize.y; + } + + sizeX *= tileSizeFac; + sizeY *= tileSizeFac; + /* // Add an extra 1/2 pixel as an ugly fix to the tile gap pb in some browsers if (L.Browser.chrome || L.Browser.safari) { sizeX += 0.5; sizeY += 0.5; } */ - tile.style.width = sizeX + 'px'; - tile.style.height = sizeY + 'px'; - - tile.onselectstart = L.Util.falseFn; - tile.onmousemove = L.Util.falseFn; - - // update opacity on tiles in IE7-8 because of filter inheritance problems - if (L.Browser.ielt9 && this.options.opacity < 1) { - L.DomUtil.setOpacity(tile, this.options.opacity); - } - - // without this hack, tiles disappear after zoom on Chrome for Android - // https://github.com/Leaflet/Leaflet/issues/2078 - if (L.Browser.android && !L.Browser.android23) { - tile.style.WebkitBackfaceVisibility = 'hidden'; - } - }, + tile.style.width = sizeX + 'px'; + tile.style.height = sizeY + 'px'; + + tile.onselectstart = L.Util.falseFn; + tile.onmousemove = L.Util.falseFn; + + // update opacity on tiles in IE7-8 because of filter inheritance problems + if (L.Browser.ielt9 && this.options.opacity < 1) { + L.DomUtil.setOpacity(tile, this.options.opacity); + } + + // without this hack, tiles disappear after zoom on Chrome for Android + // https://github.com/Leaflet/Leaflet/issues/2078 + if (L.Browser.android && !L.Browser.android23) { + tile.style.WebkitBackfaceVisibility = 'hidden'; + } + } + }); L.tileLayer.iip = function (url, options) { - return new L.TileLayer.IIP(url, options); + return new L.TileLayer.IIP(url, options); }; + /* # L.RGB.js manages RGB triplets # @@ -1847,67 +1594,55 @@ L.tileLayer.iip = function (url, options) { */ L.RGB = function (r, g, b) { - this.r = r; - this.g = g; - this.b = b; + this.r = r; + this.g = g; + this.b = b; }; L.RGB.prototype = { - clone: function () { - return new L.RGB(this.r, this.g, this.b); - }, - - toStr: function () { - var r = Math.round(this.r * 255.0), - g = Math.round(this.g * 255.0), - b = Math.round(this.b * 255.0); - - if (r < 0.0) { - r = 0.0; - } else if (r > 255.0) { - r = 255.0; - } - if (g < 0.0) { - g = 0.0; - } else if (g > 255.0) { - g = 255.0; - } - if (b < 0.0) { - b = 0.0; - } else if (b > 255.0) { - b = 255.0; - } - - return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); - }, - - isOn: function () { - return this.r > 0.0 || this.g > 0.0 || this.b > 0.0 ? true : false; - }, + + clone: function () { + return new L.RGB(this.r, this.g, this.b); + }, + + toStr: function () { + var r = Math.round(this.r * 255.0), + g = Math.round(this.g * 255.0), + b = Math.round(this.b * 255.0); + + if (r < 0.0) { r = 0.0; } else if (r > 255.0) { r = 255.0; } + if (g < 0.0) { g = 0.0; } else if (g > 255.0) { g = 255.0; } + if (b < 0.0) { b = 0.0; } else if (b > 255.0) { b = 255.0; } + + return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); + }, + + isOn: function () { + return (this.r > 0.0 || this.g > 0.0 || this.b > 0.0) ? true : false; + } }; L.rgb = function (r, g, b) { - if (r instanceof L.RGB) { - return r; - } - if (typeof r === 'string') { - var bigint = parseInt('0x' + r.slice(1), 16); - - return new L.RGB( - ((bigint >> 16) & 255) / 255.0, - ((bigint >> 8) & 255) / 255.0, - (bigint & 255) / 255.0 - ); - } - if (L.Util.isArray(r)) { - return new L.RGB(r[0], r[1], r[2]); - } - if (r === undefined || r === null) { - return r; - } - return new L.RGB(r, g, b); + if (r instanceof L.RGB) { + return r; + } + if (typeof r === 'string') { + var bigint = parseInt('0x' + r.slice(1), 16); + + return new L.RGB(((bigint >> 16) & 255) / 255.0, + ((bigint >> 8) & 255) / 255.0, + (bigint & 255) / 255.0); + } + if (L.Util.isArray(r)) { + return new L.RGB(r[0], r[1], r[2]); + } + if (r === undefined || r === null) { + return r; + } + return new L.RGB(r, g, b); }; + /* # Add an ellipse defined by its semi-major and semi-minor axes (in pixels), as # well as a position angle in degrees (CCW from x axis). @@ -1920,178 +1655,163 @@ L.rgb = function (r, g, b) { */ L.EllipseMarker = L.Path.extend({ - CANVAS: true, - SVG: false, - - options: { - fill: true, - majAxis: 10, - minAxis: 10, - posAngle: 0, - }, - - initialize: function (latlng, options) { - L.setOptions(this, options); - - this._majAxis = this.options.majAxis; - this._minAxis = this.options.majAxis; - this._posAngle = this.options.posAngle; - this._latlng = L.latLng(latlng); - - var deg = Math.PI / 180.0, - cpa = Math.cos(this._posAngle * deg), - spa = Math.sin(this._posAngle * deg), - cpa2 = cpa * cpa, - spa2 = spa * spa, - a2 = this._majAxis * this._majAxis, - b2 = this._minAxis * this._minAxis, - mx2 = a2 * cpa2 + b2 * spa2, - my2 = a2 * spa2 + b2 * cpa2, - mxy = (a2 - b2) * cpa * spa, - c = mx2 * my2 - mxy * mxy; - - this._limX = Math.sqrt(mx2); - this._limY = Math.sqrt(my2); - // Manage ellipses with minor axis = 0 - if (c <= 0.0) { - mx2 += 1.0; - my2 += 1.0; - c = mx2 * my2 - mxy * mxy; - } - // Necessary for computing the exact ellipse boundaries - this._cXX = my2 / c; - this._cYY = mx2 / c; - this._cXY = (-2.0 * mxy) / c; - }, - - setLatLng: function (latlng) { - this._latlng = L.latLng(latlng); - this.redraw(); - return this.fire('move', { latlng: this._latlng }); - }, - - getLatLng: function () { - return this._latlng; - }, - - setParams: function (ellipseParams) { - this.options.majAxis = this._majAxis = ellipseParams.majAxis; - this.options.minAxis = this._minAxis = ellipseParams.minAxis; - this.options.posAngle = this._posAngle = ellipseParams.posAngle; - return this.redraw(); - }, - - getParams: function () { - var ellipseParams; - - ellipseParams.majAxis = this._majAxis; - ellipseParams.minAxis = this._minAxis; - ellipseParams.posAngle = this._posAngle; - return ellipseParams; - }, - - setStyle: L.Path.prototype.setStyle, - - _project: function () { - this._point = this._map.latLngToLayerPoint(this._latlng); - this._updateBounds(); - }, - - _updateBounds: function () { - var w = this._clickTolerance(), - p = [this._limX + w, this._limY + w]; - this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p)); - }, - - _update: function () { - if (this._map) { - this._updatePath(); - } - }, - - _updatePath: function () { - this._renderer._updateEllipse(this); - }, - - _empty: function () { - return this._majAxis && !this._renderer._bounds.intersects(this._pxBounds); - }, - - _containsPoint: function (p) { - var dp = p.subtract(this._point), - ct = this._clickTolerance(), - dx = Math.abs(dp.x) - ct, - dy = Math.abs(dp.y) - ct; - - return ( - this._cXX * (dx > 0.0 ? dx * dx : 0.0) + - this._cYY * (dy > 0.0 ? dy * dy : 0.0) + - this._cXY * (dp.x * dp.y) <= - 1.0 - ); - }, + CANVAS: true, + SVG: false, + + options: { + fill: true, + majAxis: 10, + minAxis: 10, + posAngle: 0 + }, + + initialize: function (latlng, options) { + L.setOptions(this, options); + + this._majAxis = this.options.majAxis; + this._minAxis = this.options.majAxis; + this._posAngle = this.options.posAngle; + this._latlng = L.latLng(latlng); + + var deg = Math.PI / 180.0, + cpa = Math.cos(this._posAngle * deg), + spa = Math.sin(this._posAngle * deg), + cpa2 = cpa * cpa, + spa2 = spa * spa, + a2 = this._majAxis * this._majAxis, + b2 = this._minAxis * this._minAxis, + mx2 = a2 * cpa2 + b2 * spa2, + my2 = a2 * spa2 + b2 * cpa2, + mxy = (a2 - b2) * cpa * spa, + c = mx2 * my2 - mxy * mxy; + + this._limX = Math.sqrt(mx2); + this._limY = Math.sqrt(my2); + // Manage ellipses with minor axis = 0 + if (c <= 0.0) { + mx2 += 1.0; + my2 += 1.0; + c = mx2 * my2 - mxy * mxy; + } + // Necessary for computing the exact ellipse boundaries + this._cXX = my2 / c; + this._cYY = mx2 / c; + this._cXY = -2.0 * mxy / c; + }, + + setLatLng: function (latlng) { + this._latlng = L.latLng(latlng); + this.redraw(); + return this.fire('move', { latlng: this._latlng }); + }, + + getLatLng: function () { + return this._latlng; + }, + + setParams: function (ellipseParams) { + this.options.majAxis = this._majAxis = ellipseParams.majAxis; + this.options.minAxis = this._minAxis = ellipseParams.minAxis; + this.options.posAngle = this._posAngle = ellipseParams.posAngle; + return this.redraw(); + }, + + getParams: function () { + var ellipseParams; + + ellipseParams.majAxis = this._majAxis; + ellipseParams.minAxis = this._minAxis; + ellipseParams.posAngle = this._posAngle; + return ellipseParams; + }, + + setStyle: L.Path.prototype.setStyle, + + _project: function () { + this._point = this._map.latLngToLayerPoint(this._latlng); + this._updateBounds(); + }, + + _updateBounds: function () { + var w = this._clickTolerance(), + p = [this._limX + w, this._limY + w]; + this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p)); + }, + + _update: function () { + if (this._map) { + this._updatePath(); + } + }, + + _updatePath: function () { + this._renderer._updateEllipse(this); + }, + + _empty: function () { + return this._majAxis && !this._renderer._bounds.intersects(this._pxBounds); + }, + + _containsPoint: function (p) { + var dp = p.subtract(this._point), + ct = this._clickTolerance(), + dx = Math.abs(dp.x) - ct, + dy = Math.abs(dp.y) - ct; + + return this._cXX * (dx > 0.0 ? dx * dx : 0.0) + + this._cYY * (dy > 0.0 ? dy * dy : 0.0) + this._cXY * (dp.x * dp.y) <= 1.0; + } }); L.ellipseMarker = function (latlng, options) { - return new L.EllipseMarker(latlng, options); + return new L.EllipseMarker(latlng, options); }; L.Canvas.include({ - _updateEllipse: function (layer) { - if (layer._empty()) { - return; - } - - var p = layer._point, - ctx = this._ctx, - r = layer._minAxis, - s = layer._majAxis / layer._minAxis; - - ctx.save(); - ctx.translate(p.x, p.y); - ctx.rotate((layer._posAngle * Math.PI) / 180.0); - ctx.scale(1, s); - - ctx.beginPath(); - ctx.arc(0, 0, r, 0, Math.PI * 2, false); - ctx.restore(); - - this._fillStroke(ctx, layer); - }, + _updateEllipse: function (layer) { + + if (layer._empty()) { return; } + + var p = layer._point, + ctx = this._ctx, + r = layer._minAxis, + s = layer._majAxis / layer._minAxis; + + ctx.save(); + ctx.translate(p.x, p.y); + ctx.rotate(layer._posAngle * Math.PI / 180.0); + ctx.scale(1, s); + + ctx.beginPath(); + ctx.arc(0, 0, r, 0, Math.PI * 2, false); + ctx.restore(); + + this._fillStroke(ctx, layer); + } }); L.SVG.include({ - _updateEllipse: function (layer) { - var deg = Math.PI / 180.0, - p = layer._point, - r = layer._minAxis, - r2 = layer._majAxis, - dx = r * Math.cos(layer._posAngle * deg), - dy = r * Math.sin(layer._posAngle * deg), - arc = 'a' + r + ',' + r2 + ' ' + layer._posAngle + ' 1,0 '; - - // drawing a circle with two half-arcs - var d = layer._empty() - ? 'M0 0' - : 'M' + - (p.x - dx) + - ',' + - (p.y - dy) + - arc + - dx * 2 + - ',' + - dy * 2 + - ' ' + - arc + - -dx * 2 + - ',' + - -dy * 2 + - ' '; - - this._setPath(layer, d); - }, + _updateEllipse: function (layer) { + var deg = Math.PI / 180.0, + p = layer._point, + r = layer._minAxis, + r2 = layer._majAxis, + dx = r * Math.cos(layer._posAngle * deg), + dy = r * Math.sin(layer._posAngle * deg), + arc = 'a' + r + ',' + r2 + ' ' + layer._posAngle + ' 1,0 '; + + // drawing a circle with two half-arcs + var d = layer._empty() ? 'M0 0' : + 'M' + (p.x - dx) + ',' + (p.y - dy) + + arc + (dx * 2) + ',' + (dy * 2) + ' ' + + arc + (-dx * 2) + ',' + (-dy * 2) + ' '; + + this._setPath(layer, d); + } }); + + /* # Add an ellipse defined by its semi-major and semi-minor axes (in degrees), as # well as a position angle in degrees (east of north). @@ -2104,107 +1824,102 @@ L.SVG.include({ */ L.Ellipse = L.EllipseMarker.extend({ - options: { - fill: true, - }, - - initialize: function (latlng, options) { - L.setOptions(this, options); - - var deg = Math.PI / 180.0, - cpa = Math.cos(this.options.posAngle * deg), - spa = Math.sin(this.options.posAngle * deg), - cpa2 = cpa * cpa, - spa2 = spa * spa, - a2 = this.options.majAxis * this.options.majAxis, - b2 = this.options.minAxis * this.options.minAxis; - this._latlng = L.latLng(latlng); - // Compute quadratic forms to be used for coordinate transforms - this._mLat2 = a2 * cpa2 + b2 * spa2; - this._mLng2 = a2 * spa2 + b2 * cpa2; - this._mLatLng = (a2 - b2) * cpa * spa; - }, - - getBounds: function () { - var half = [this._limX, this._limY]; - - return new L.LatLngBounds( - this._map.layerPointToLatLng(this._point.subtract(half)), - this._map.layerPointToLatLng(this._point.add(half)) - ); - }, - - _project: function () { - var map = this._map, - crs = map.options.crs; - - this._point = map.latLngToLayerPoint(this._latlng); - if (!this._majAxis1) { - var lng = this._latlng.lng, - lat = this._latlng.lat, - deg = Math.PI / 180.0, - clat = Math.cos(lat * deg), - dl = lat < 90.0 ? 0.001 : -0.001, - point = crs.project(this._latlng), - dpointdlat = crs.project(L.latLng(lat + dl, lng)).subtract(point), - dpointdlng = crs - .project(L.latLng(lat, lng + (dl * 1.0) / (clat > dl ? clat : dl))) - .subtract(point), - c11 = dpointdlat.x / dl, - c12 = dpointdlng.x / dl, - c21 = dpointdlat.y / dl, - c22 = dpointdlng.y / dl, - mx2 = - c11 * c11 * this._mLat2 + - c12 * c12 * this._mLng2 + - 2.0 * c11 * c12 * this._mLatLng, - my2 = - c21 * c21 * this._mLat2 + - c22 * c22 * this._mLng2 + - 2.0 * c21 * c22 * this._mLatLng, - mxy = - c11 * c21 * this._mLat2 + - c12 * c22 * this._mLng2 + - (c11 * c22 + c12 * c21) * this._mLatLng, - a1 = 0.5 * (mx2 + my2), - a2 = Math.sqrt(0.25 * (mx2 - my2) * (mx2 - my2) + mxy * mxy), - a3 = mx2 * my2 - mxy * mxy; - this._majAxis = this._majAxis1 = Math.sqrt(a1 + a2); - this._minAxis = this._minAxis1 = a1 > a2 ? Math.sqrt(a1 - a2) : 0.0; - this._posAngle = (0.5 * Math.atan2(2.0 * mxy, mx2 - my2)) / deg; - this._limX = this._limX1 = Math.sqrt(mx2); - this._limY = this._limY1 = Math.sqrt(my2); - // Manage ellipses with minor axis = 0 - if (a3 <= 0.0) { - mx2 += 1.0; - my2 += 1.0; - a3 = mx2 * my2 - mxy * mxy; - } - // Necessary for computing the exact ellipse boundaries - this._cXX1 = my2 / a3; - this._cYY1 = mx2 / a3; - this._cXY1 = (-2.0 * mxy) / a3; - } - - var scale = crs.scale(map._zoom), - invscale2 = 1.0 / (scale * scale); - // Ellipse parameters have already - this._majAxis = this._majAxis1 * scale; - this._minAxis = this._minAxis1 * scale; - this._limX = this._limX1 * scale; - this._limY = this._limY1 * scale; - this._cXX = this._cXX1 * invscale2; - this._cYY = this._cYY1 * invscale2; - this._cXY = this._cXY1 * invscale2; - - this._updateBounds(); - }, + + options: { + fill: true + }, + + initialize: function (latlng, options) { + L.setOptions(this, options); + + var deg = Math.PI / 180.0, + cpa = Math.cos(this.options.posAngle * deg), + spa = Math.sin(this.options.posAngle * deg), + cpa2 = cpa * cpa, + spa2 = spa * spa, + a2 = this.options.majAxis * this.options.majAxis, + b2 = this.options.minAxis * this.options.minAxis; + this._latlng = L.latLng(latlng); + // Compute quadratic forms to be used for coordinate transforms + this._mLat2 = a2 * cpa2 + b2 * spa2; + this._mLng2 = a2 * spa2 + b2 * cpa2; + this._mLatLng = (a2 - b2) * cpa * spa; + }, + + getBounds: function () { + var half = [this._limX, this._limY]; + + return new L.LatLngBounds( + this._map.layerPointToLatLng(this._point.subtract(half)), + this._map.layerPointToLatLng(this._point.add(half))); + }, + + _project: function () { + var map = this._map, + crs = map.options.crs; + + this._point = map.latLngToLayerPoint(this._latlng); + if (!this._majAxis1) { + var lng = this._latlng.lng, + lat = this._latlng.lat, + deg = Math.PI / 180.0, + clat = Math.cos(lat * deg), + dl = lat < 90.0 ? 0.001 : -0.001, + point = crs.project(this._latlng), + dpointdlat = crs.project(L.latLng(lat + dl, lng)).subtract(point), + dpointdlng = crs.project(L.latLng(lat, lng + dl * 1.0 / + (clat > dl ? clat : dl))).subtract(point), + c11 = dpointdlat.x / dl, + c12 = dpointdlng.x / dl, + c21 = dpointdlat.y / dl, + c22 = dpointdlng.y / dl, + mx2 = c11 * c11 * this._mLat2 + c12 * c12 * this._mLng2 + + 2.0 * c11 * c12 * this._mLatLng, + my2 = c21 * c21 * this._mLat2 + c22 * c22 * this._mLng2 + + 2.0 * c21 * c22 * this._mLatLng, + mxy = c11 * c21 * this._mLat2 + c12 * c22 * this._mLng2 + + (c11 * c22 + c12 * c21) * this._mLatLng, + a1 = 0.5 * (mx2 + my2), + a2 = Math.sqrt(0.25 * (mx2 - my2) * (mx2 - my2) + mxy * mxy), + a3 = mx2 * my2 - mxy * mxy; + this._majAxis = this._majAxis1 = Math.sqrt(a1 + a2); + this._minAxis = this._minAxis1 = a1 > a2 ? Math.sqrt(a1 - a2) : 0.0; + this._posAngle = 0.5 * Math.atan2(2.0 * mxy, mx2 - my2) / deg; + this._limX = this._limX1 = Math.sqrt(mx2); + this._limY = this._limY1 = Math.sqrt(my2); + // Manage ellipses with minor axis = 0 + if (a3 <= 0.0) { + mx2 += 1.0; + my2 += 1.0; + a3 = mx2 * my2 - mxy * mxy; + } + // Necessary for computing the exact ellipse boundaries + this._cXX1 = my2 / a3; + this._cYY1 = mx2 / a3; + this._cXY1 = -2.0 * mxy / a3; + } + + var scale = crs.scale(map._zoom), + invscale2 = 1.0 / (scale * scale); + // Ellipse parameters have already + this._majAxis = this._majAxis1 * scale; + this._minAxis = this._minAxis1 * scale; + this._limX = this._limX1 * scale; + this._limY = this._limY1 * scale; + this._cXX = this._cXX1 * invscale2; + this._cYY = this._cYY1 * invscale2; + this._cXY = this._cXY1 * invscale2; + + this._updateBounds(); + } }); L.ellipse = function (latlng, options) { - return new L.Ellipse(latlng, options); + return new L.Ellipse(latlng, options); }; + + /* # L.Catalog contains specific catalog settings and conversion tools. # @@ -2217,473 +1932,350 @@ L.ellipse = function (latlng, options) { */ L.Catalog = { - nmax: 10000, // Sets the maximum number of sources per query - - _csvToGeoJSON: function (str) { - // Check to see if the delimiter is defined. If not, then default to comma. - var badreg = new RegExp('#|--|objName|string|^$'), - lines = str.split('\n'), - geo = { type: 'FeatureCollection', features: [] }; - - for (var i in lines) { - var line = lines[i]; - if (badreg.test(line) === false) { - var feature = { - type: 'Feature', - id: '', - properties: { - items: [], - }, - geometry: { - type: 'Point', - coordinates: [0.0, 0.0], - }, - }, - geometry = feature.geometry, - properties = feature.properties; - - var cell = line.split(/[,;\t]/); - feature.id = cell[0]; - geometry.coordinates[0] = parseFloat(cell[1]); - geometry.coordinates[1] = parseFloat(cell[2]); - var items = cell.slice(3), - item; - for (var j in items) { - properties.items.push(this.readProperty(items[j])); - } - geo.features.push(feature); - } - } - return geo; - }, - - readProperty: function (item) { - var fitem = parseFloat(item); - return isNaN(fitem) ? '--' : fitem; - }, - - toGeoJSON: function (str) { - return this._csvToGeoJSON(str); - }, - - popup: function (feature) { - var str = ''; - } else { - str += 'ID: ' + feature.id + ''; - } - str += - '' + - ''; - for (var i in this.properties) { - if (this.propertyMask === undefined || this.propertyMask[i] === true) { - str += - '' + - ''; - } - } - str += '
' + - this.properties[i] + - ':' + - feature.properties.items[i].toString() + - ' '; - if (this.units[i]) { - str += this.units[i]; - } - str += '
'; - return str; - }, - - draw: function (feature, latlng) { - var refmag = feature.properties.items[this.magindex ? this.magindex : 0]; - return L.circleMarker(latlng, { - radius: refmag ? this.maglim + 5 - refmag : 8, - }); - }, - - filter: function (feature) { - return true; - }, - - vizierURL: 'https://vizier.unistra.fr/viz-bin', - mastURL: 'https://archive.stsci.edu', + nmax: 10000, // Sets the maximum number of sources per query + + _csvToGeoJSON: function (str) { + // Check to see if the delimiter is defined. If not, then default to comma. + var badreg = new RegExp('#|--|objName|string|^$'), + lines = str.split('\n'), + geo = { type: 'FeatureCollection', features: [] }; + + for (var i in lines) { + var line = lines[i]; + if (badreg.test(line) === false) { + var feature = { + type: 'Feature', + id: '', + properties: { + items: [] + }, + geometry: { + type: 'Point', + coordinates: [0.0, 0.0] + } + }, + geometry = feature.geometry, + properties = feature.properties; + + var cell = line.split(/[,;\t]/); + feature.id = cell[0]; + geometry.coordinates[0] = parseFloat(cell[1]); + geometry.coordinates[1] = parseFloat(cell[2]); + var items = cell.slice(3), + item; + for (var j in items) { + properties.items.push(this.readProperty(items[j])); + } + geo.features.push(feature); + } + } + return geo; + }, + + readProperty: function (item) { + var fitem = parseFloat(item); + return isNaN(fitem) ? '--' : fitem; + }, + + toGeoJSON: function (str) { + return this._csvToGeoJSON(str); + }, + + popup: function (feature) { + var str = '
'; + if (this.objurl) { + str += 'ID: ' + feature.id + '
'; + } else { + str += 'ID: ' + feature.id + ''; + } + str += '' + + ''; + for (var i in this.properties) { + if (this.propertyMask === undefined || this.propertyMask[i] === true) { + str += '' + + ''; + } + } + str += '
' + this.properties[i] + ':' + feature.properties.items[i].toString() + ' '; + if (this.units[i]) { + str += this.units[i]; + } + str += '
'; + return str; + + }, + + draw: function (feature, latlng) { + var refmag = feature.properties.items[this.magindex ? this.magindex : 0]; + return L.circleMarker(latlng, { + radius: refmag ? this.maglim + 5 - refmag : 8 + }); + }, + + filter: function (feature) { + return true; + }, + + vizierURL: 'https://vizier.unistra.fr/viz-bin', + mastURL: 'https://archive.stsci.edu' + }; L.Catalog['2MASS'] = L.extend({}, L.Catalog, { - service: 'Vizier@CDS', - name: '2MASS', - className: 'logo-catalog-vizier', - attribution: '2MASS All-Sky Catalog of Point Sources (Cutri et al. 2003)', - color: 'red', - maglim: 17.0, - regionType: 'box', - url: - L.Catalog.vizierURL + - '/asu-tsv?&-mime=csv&-source=II/246&' + - '-out=2MASS,RAJ2000,DEJ2000,Jmag,Hmag,Kmag&-out.meta=&' + - '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&' + - '-out.max={nmax}&-sort=Jmag', - properties: ['J', 'H', 'K'], - units: ['', '', ''], - objurl: - L.Catalog.vizierURL + - '/VizieR-5?-source=II/246&-c={ra},{dec},eq=J2000&-c.rs=0.01', + service: 'Vizier@CDS', + name: '2MASS', + className: 'logo-catalog-vizier', + attribution: '2MASS All-Sky Catalog of Point Sources (Cutri et al. 2003)', + color: 'red', + maglim: 17.0, + regionType: 'box', + url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=II/246&' + + '-out=2MASS,RAJ2000,DEJ2000,Jmag,Hmag,Kmag&-out.meta=&' + + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&' + + '-out.max={nmax}&-sort=Jmag', + properties: ['J', 'H', 'K'], + units: ['', '', ''], + objurl: L.Catalog.vizierURL + '/VizieR-5?-source=II/246&-c={ra},{dec},eq=J2000&-c.rs=0.01' }); L.Catalog.SDSS = L.extend({}, L.Catalog, { - service: 'Vizier@CDS', - name: 'SDSS release 12', - className: 'logo-catalog-vizier', - attribution: 'SDSS Photometric Catalog, Release 9 (Alam et al. 2015)', - color: 'yellow', - maglim: 25.0, - regionType: 'box', - url: - L.Catalog.vizierURL + - '/asu-tsv?&-mime=csv&-source=V/147&' + - '-out=SDSS12,RA_ICRS,DE_ICRS,umag,gmag,rmag,imag,zmag&-out.meta=&' + - '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=rmag', - properties: ['u', 'g', 'r', 'i', 'z'], - units: ['', '', '', '', ''], - objurl: - L.Catalog.vizierURL + - '/VizieR-5?-source=V/147/sdss12&-c={ra},{dec},eq=J2000&-c.rs=0.01', + service: 'Vizier@CDS', + name: 'SDSS release 12', + className: 'logo-catalog-vizier', + attribution: 'SDSS Photometric Catalog, Release 9 (Alam et al. 2015)', + color: 'yellow', + maglim: 25.0, + regionType: 'box', + url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=V/147&' + + '-out=SDSS12,RA_ICRS,DE_ICRS,umag,gmag,rmag,imag,zmag&-out.meta=&' + + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=rmag', + properties: ['u', 'g', 'r', 'i', 'z'], + units: ['', '', '', '', ''], + objurl: L.Catalog.vizierURL + '/VizieR-5?-source=V/147/sdss12&-c={ra},{dec},eq=J2000&-c.rs=0.01' }); L.Catalog.PPMXL = L.extend({}, L.Catalog, { - service: 'Vizier@CDS', - name: 'PPMXL', - className: 'logo-catalog-vizier', - attribution: - 'PPM-Extended, positions and proper motions (Roeser et al. 2008)', - color: 'green', - maglim: 20.0, - regionType: 'box', - url: - L.Catalog.vizierURL + - '/asu-tsv?&-mime=csv&-source=I/317&' + - '-out=PPMXL,RAJ2000,DEJ2000,Jmag,Hmag,Kmag,b1mag,b2mag,r1mag,r2mag,imag,pmRA,pmDE&-out.meta=&' + - '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=Jmag', - properties: [ - 'J', - 'H', - 'K', - 'b1', - 'b2', - 'r1', - 'r2', - 'i', - 'μɑ cos δ', - 'μδ', - ], - units: ['', '', '', '', '', '', '', '', 'mas/yr', 'mas/yr'], - objurl: - L.Catalog.vizierURL + - '/VizieR-5?-source=I/317&-c={ra},{dec},eq=J2000&-c.rs=0.01', + service: 'Vizier@CDS', + name: 'PPMXL', + className: 'logo-catalog-vizier', + attribution: 'PPM-Extended, positions and proper motions (Roeser et al. 2008)', + color: 'green', + maglim: 20.0, + regionType: 'box', + url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=I/317&' + + '-out=PPMXL,RAJ2000,DEJ2000,Jmag,Hmag,Kmag,b1mag,b2mag,r1mag,r2mag,imag,pmRA,pmDE&-out.meta=&' + + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=Jmag', + properties: ['J', 'H', 'K', 'b1', 'b2', 'r1', + 'r2', 'i', + 'μɑ cos δ', 'μδ'], + units: ['', '', '', '', '', '', '', '', 'mas/yr', 'mas/yr'], + objurl: L.Catalog.vizierURL + '/VizieR-5?-source=I/317&-c={ra},{dec},eq=J2000&-c.rs=0.01' }); L.Catalog.Abell = L.extend({}, L.Catalog, { - service: 'Vizier@CDS', - name: 'Abell clusters', - className: 'logo-catalog-vizier', - attribution: 'Rich Clusters of Galaxies (Abell et al. 1989) ', - color: 'orange', - maglim: 30.0, - regionType: 'box', - url: - L.Catalog.vizierURL + - '/asu-tsv?&-mime=csv&-source=VII/110A&' + - '-out=ACO,_RAJ2000,_DEJ2000,m10,Rich,Dclass&-out.meta=&' + - '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=m10', - properties: ['m10', 'Richness', 'Dclass'], - units: ['', '', ''], - objurl: - L.Catalog.vizierURL + - '/VizieR-5?-source=VII/110A&-c={ra},{dec},eq=J2000&-c.rs=0.2', + service: 'Vizier@CDS', + name: 'Abell clusters', + className: 'logo-catalog-vizier', + attribution: 'Rich Clusters of Galaxies (Abell et al. 1989) ', + color: 'orange', + maglim: 30.0, + regionType: 'box', + url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=VII/110A&' + + '-out=ACO,_RAJ2000,_DEJ2000,m10,Rich,Dclass&-out.meta=&' + + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=m10', + properties: ['m10', 'Richness', 'Dclass'], + units: ['', '', ''], + objurl: L.Catalog.vizierURL + '/VizieR-5?-source=VII/110A&-c={ra},{dec},eq=J2000&-c.rs=0.2' }); L.Catalog.NVSS = L.extend({}, L.Catalog, { - service: 'Vizier@CDS', - name: 'NVSS', - className: 'logo-catalog-vizier', - attribution: '1.4GHz NRAO VLA Sky Survey (NVSS) (Condon et al. 1998)', - color: 'magenta', - maglim: 30.0, - regionType: 'box', - url: - L.Catalog.vizierURL + - '/asu-tsv?&-mime=csv&-source=VIII/65/NVSS&' + - '-out=NVSS,_RAJ2000,_DEJ2000,S1.4,MajAxis,MinAxis,PA&-out.meta=&' + - '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=-S1.4', - properties: [ - 'S1.4GHz', - 'Major axis', - 'Minor axis', - 'Position angle', - ], - units: ['mJy', '″', '″', '°'], - objurl: - L.Catalog.vizierURL + - '/VizieR-5?-source=VIII/65/NVSS&-c={ra},{dec},eq=J2000&-c.rs=0.2', - draw: function (feature, latlng) { - return L.ellipse(latlng, { - majAxis: feature.properties.items[1] / 7200.0, - minAxis: feature.properties.items[2] / 7200.0, - posAngle: - feature.properties.items[3] === '--' - ? 0.0 - : feature.properties.items[3], - }); - }, + service: 'Vizier@CDS', + name: 'NVSS', + className: 'logo-catalog-vizier', + attribution: '1.4GHz NRAO VLA Sky Survey (NVSS) (Condon et al. 1998)', + color: 'magenta', + maglim: 30.0, + regionType: 'box', + url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=VIII/65/NVSS&' + + '-out=NVSS,_RAJ2000,_DEJ2000,S1.4,MajAxis,MinAxis,PA&-out.meta=&' + + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=-S1.4', + properties: ['S1.4GHz', 'Major axis', 'Minor axis', 'Position angle'], + units: ['mJy', '″', '″', '°'], + objurl: L.Catalog.vizierURL + '/VizieR-5?-source=VIII/65/NVSS&-c={ra},{dec},eq=J2000&-c.rs=0.2', + draw: function (feature, latlng) { + return L.ellipse(latlng, { + majAxis: feature.properties.items[1] / 7200.0, + minAxis: feature.properties.items[2] / 7200.0, + posAngle: feature.properties.items[3] === '--' ? 0.0 : feature.properties.items[3] + }); + } }); L.Catalog.FIRST = L.extend({}, L.Catalog, { - service: 'Vizier@CDS', - name: 'FIRST', - className: 'logo-catalog-vizier', - attribution: 'The FIRST Survey Catalog (Helfand et al. 2015)', - color: 'blue', - maglim: 30.0, - regionType: 'box', - url: - L.Catalog.vizierURL + - '/asu-tsv?&-mime=csv&-source=VIII/92/first14&' + - '-out=FIRST,_RAJ2000,_DEJ2000,Fpeak,fMaj,fMin,fPA&-out.meta=&' + - '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=-Fpeak', - properties: [ - 'Fpeak(1.4GHz)', - 'Major axis FWHM', - 'Minor axis FWHM', - 'Position angle', - ], - units: ['mJy', '″', '″', '°'], - objurl: - L.Catalog.vizierURL + - '/VizieR-5?-source=VIII/92/first14&-c={ra},{dec},eq=J2000&-c.rs=0.2', - draw: function (feature, latlng) { - return L.ellipse(latlng, { - majAxis: feature.properties.items[1] / 7200.0, - minAxis: feature.properties.items[2] / 7200.0, - posAngle: - feature.properties.items[3] === '--' - ? 0.0 - : feature.properties.items[3], - }); - }, + service: 'Vizier@CDS', + name: 'FIRST', + className: 'logo-catalog-vizier', + attribution: 'The FIRST Survey Catalog (Helfand et al. 2015)', + color: 'blue', + maglim: 30.0, + regionType: 'box', + url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=VIII/92/first14&' + + '-out=FIRST,_RAJ2000,_DEJ2000,Fpeak,fMaj,fMin,fPA&-out.meta=&' + + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=-Fpeak', + properties: ['Fpeak(1.4GHz)', 'Major axis FWHM', 'Minor axis FWHM', 'Position angle'], + units: ['mJy', '″', '″', '°'], + objurl: L.Catalog.vizierURL + '/VizieR-5?-source=VIII/92/first14&-c={ra},{dec},eq=J2000&-c.rs=0.2', + draw: function (feature, latlng) { + return L.ellipse(latlng, { + majAxis: feature.properties.items[1] / 7200.0, + minAxis: feature.properties.items[2] / 7200.0, + posAngle: feature.properties.items[3] === '--' ? 0.0 : feature.properties.items[3] + }); + } }); L.Catalog.AllWISE = L.extend({}, L.Catalog, { - service: 'Vizier@CDS', - name: 'AllWISE', - className: 'logo-catalog-vizier', - attribution: 'AllWISE Data Release (Cutri et al. 2013)', - color: 'red', - maglim: 18.0, - regionType: 'box', - url: - L.Catalog.vizierURL + - '/asu-tsv?&-mime=csv&-source=II/328/allwise&' + - '-out=AllWISE,_RAJ2000,_DEJ2000,W1mag,W2mag,W3mag,W4mag&-out.meta=&' + - '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=W1mag', - properties: [ - 'W1mag (3.4µm)', - 'W2mag (4.6µm)', - 'W3mag (12µm)', - 'W4mag (22µm)', - ], - units: ['', '', '', ''], - objurl: - L.Catalog.vizierURL + - '/VizieR-5?-source=II/328/allwise&-c={ra},{dec},eq=J2000&-c.rs=0.2', + service: 'Vizier@CDS', + name: 'AllWISE', + className: 'logo-catalog-vizier', + attribution: 'AllWISE Data Release (Cutri et al. 2013)', + color: 'red', + maglim: 18.0, + regionType: 'box', + url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=II/328/allwise&' + + '-out=AllWISE,_RAJ2000,_DEJ2000,W1mag,W2mag,W3mag,W4mag&-out.meta=&' + + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=W1mag', + properties: ['W1mag (3.4µm)', 'W2mag (4.6µm)', + 'W3mag (12µm)', 'W4mag (22µm)'], + units: ['', '', '', ''], + objurl: L.Catalog.vizierURL + '/VizieR-5?-source=II/328/allwise&-c={ra},{dec},eq=J2000&-c.rs=0.2' }); L.Catalog.GALEX_AIS = L.extend({}, L.Catalog, { - service: 'Vizier@CDS', - name: 'GALEX AIS', - className: 'logo-catalog-vizier', - attribution: - 'GALEX catalogs of UV sources: All-sky Imaging Survey (Bianchi et al. 2011)', - color: 'magenta', - maglim: 21.0, - regionType: 'box', - url: - L.Catalog.vizierURL + - '/asu-tsv?&-mime=csv&-source=II/312/ais&' + - '-out=objid,_RAJ2000,_DEJ2000,FUV,NUV&-out.meta=&' + - '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=FUV', - properties: ['FUVAB', 'NUVAB'], - units: ['', ''], - objurl: - L.Catalog.vizierURL + - '/VizieR-5?-source=II/312/ais&-c={ra},{dec},eq=J2000&-c.rs=0.2', + service: 'Vizier@CDS', + name: 'GALEX AIS', + className: 'logo-catalog-vizier', + attribution: 'GALEX catalogs of UV sources: All-sky Imaging Survey (Bianchi et al. 2011)', + color: 'magenta', + maglim: 21.0, + regionType: 'box', + url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=II/312/ais&' + + '-out=objid,_RAJ2000,_DEJ2000,FUV,NUV&-out.meta=&' + + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=FUV', + properties: ['FUVAB', 'NUVAB'], + units: ['', ''], + objurl: L.Catalog.vizierURL + '/VizieR-5?-source=II/312/ais&-c={ra},{dec},eq=J2000&-c.rs=0.2' }); L.Catalog.GAIA_DR1 = L.extend({}, L.Catalog, { - service: 'Vizier@CDS', - name: 'Gaia DR1', - className: 'logo-catalog-vizier', - attribution: 'First Gaia Data Release (2016)', - color: 'green', - maglim: 20.0, - regionType: 'box', - url: - L.Catalog.vizierURL + - '/asu-tsv?&-mime=csv&-source=I/337&' + - '-out=Source,RA_ICRS,DE_ICRS,,pmRA,pmDE&-out.meta=&' + - '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=', - properties: [ - 'G', - 'μɑ cos δ', - 'μδ', - ], - units: ['', 'mas/yr', 'mas/yr'], - objurl: - L.Catalog.vizierURL + - '/VizieR-5?-source=I/337&-c={ra},{dec},eq=J2000&-c.rs=0.01', + service: 'Vizier@CDS', + name: 'Gaia DR1', + className: 'logo-catalog-vizier', + attribution: 'First Gaia Data Release (2016)', + color: 'green', + maglim: 20.0, + regionType: 'box', + url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=I/337&' + + '-out=Source,RA_ICRS,DE_ICRS,,pmRA,pmDE&-out.meta=&' + + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=', + properties: ['G', 'μɑ cos δ', 'μδ'], + units: ['', 'mas/yr', 'mas/yr'], + objurl: L.Catalog.vizierURL + '/VizieR-5?-source=I/337&-c={ra},{dec},eq=J2000&-c.rs=0.01' }); L.Catalog.URAT_1 = L.extend({}, L.Catalog, { - service: 'Vizier@CDS', - name: 'URAT1', - className: 'logo-catalog-vizier', - attribution: - 'The first U.S. Naval Observatory Astrometric Robotic Telescope Catalog (Zacharias et al. 2015)', - color: 'yellow', - maglim: 17.0, - regionType: 'box', - url: - L.Catalog.vizierURL + - '/asu-tsv?&-mime=csv&-source=I/329&' + - '-out=URAT1,RAJ2000,DEJ2000,f.mag,pmRA,pmDE&-out.meta=&' + - '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=f.mag', - properties: [ - 'fmag', - 'μɑ cos δ', - 'μδ', - ], - units: ['', 'mas/yr', 'mas/yr'], - objurl: - L.Catalog.vizierURL + - '/VizieR-5?-source=I/329&-c={ra},{dec},eq=J2000&-c.rs=0.01', + service: 'Vizier@CDS', + name: 'URAT1', + className: 'logo-catalog-vizier', + attribution: 'The first U.S. Naval Observatory Astrometric Robotic Telescope Catalog (Zacharias et al. 2015)', + color: 'yellow', + maglim: 17.0, + regionType: 'box', + url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=I/329&' + + '-out=URAT1,RAJ2000,DEJ2000,f.mag,pmRA,pmDE&-out.meta=&' + + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=f.mag', + properties: ['fmag', 'μɑ cos δ', 'μδ'], + units: ['', 'mas/yr', 'mas/yr'], + objurl: L.Catalog.vizierURL + '/VizieR-5?-source=I/329&-c={ra},{dec},eq=J2000&-c.rs=0.01' }); L.Catalog.PanStarrs = L.extend({}, L.Catalog, { - service: 'MAST@STScI', - name: 'Pan-Starrs1', - className: 'logo-catalog-mast', - attribution: - 'The Pan-STARRS1 Database and Data Products (Flewelling et al. 2016)', - color: 'yellow', - maglim: 24.0, - magindex: 1, - regionType: 'cone', - url: - L.Catalog.mastURL + - '/panstarrs/search.php?' + - 'action=Search&target=&radius={drm}&ra={lng}&dec={lat}&equinox={sys}&nDetections=%3E+1&' + - 'selectedColumnsCsv=objname%2Cramean%2Cdecmean%2C' + - 'gmeankronmag%2Crmeankronmag%2Cimeankronmag%2Czmeankronmag%2Cymeankronmag%2CqualityFlag&' + - 'selectedColumnsList%5B%5D=rmeankronmag&availableColumns=objname&ordercolumn1=gmeankronmag&' + - 'coordformat=dec&outputformat=TSV&max_records={nmax}', - properties: ['g', 'r', 'i', 'z', 'y', 'quality'], - units: ['', '', '', '', '', ''], - propertyMask: [true, true, true, true, true, false], - readProperty: function (item) { - var fitem = parseFloat(item); - return fitem < 0.0 ? '--' : fitem; - }, - objurl: - L.Catalog.mastURL + - '/panstarrs/search.php?' + - 'action=Search&target=&radius=0.001&ra={ra}&dec={dec}&equinox=J2000&nDetections=%3E+1', - filter: function (feature) { - return parseFloat(feature.properties.items[5]) >= 40; - }, + service: 'MAST@STScI', + name: 'Pan-Starrs1', + className: 'logo-catalog-mast', + attribution: 'The Pan-STARRS1 Database and Data Products (Flewelling et al. 2016)', + color: 'yellow', + maglim: 24.0, + magindex: 1, + regionType: 'cone', + url: L.Catalog.mastURL + '/panstarrs/search.php?' + + 'action=Search&target=&radius={drm}&ra={lng}&dec={lat}&equinox={sys}&nDetections=%3E+1&' + + 'selectedColumnsCsv=objname%2Cramean%2Cdecmean%2C' + + 'gmeankronmag%2Crmeankronmag%2Cimeankronmag%2Czmeankronmag%2Cymeankronmag%2CqualityFlag&' + + 'selectedColumnsList%5B%5D=rmeankronmag&availableColumns=objname&ordercolumn1=gmeankronmag&' + + 'coordformat=dec&outputformat=TSV&max_records={nmax}', + properties: ['g', 'r', 'i', 'z', 'y', 'quality'], + units: ['', '', '', '', '', ''], + propertyMask: [true, true, true, true, true, false], + readProperty: function (item) { + var fitem = parseFloat(item); + return fitem < 0.0 ? '--' : fitem; + }, + objurl: L.Catalog.mastURL + '/panstarrs/search.php?' + + 'action=Search&target=&radius=0.001&ra={ra}&dec={dec}&equinox=J2000&nDetections=%3E+1', + filter: function (feature) { + return parseFloat(feature.properties.items[5]) >= 40; + } }); L.Catalog.GLEAM = L.extend({}, L.Catalog, { - service: 'Vizier@CDS', - name: 'GLEAM', - className: 'logo-catalog-vizier', - attribution: - 'GaLactic and Extragalactic All-sky Murchison Wide Field Array (GLEAM)' + - ' low-frequency extragalactic catalogue (Hurley-Walker et al. 2017)', - color: 'blue', - maglim: 30.0, - regionType: 'box', - url: - L.Catalog.vizierURL + - '/asu-tsv?&-mime=csv&-source=VIII/100/gleamegc&' + - '-out=GLEAM,RAJ2000,DEJ2000,Fintwide,awide,bwide,pawide&-out.meta=&' + - '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=-Fintwide', - properties: [ - 'Fint(170-231MHz)', - 'Major axis FWHM', - 'Minor axis FWHM', - 'Position angle', - ], - units: ['Jy', '″', '″', '°'], - objurl: - L.Catalog.vizierURL + - '/VizieR-5?-source=-source=VIII/100/gleamegc&-c={ra},{dec},eq=J2000&-c.rs=0.2', - draw: function (feature, latlng) { - return L.ellipse(latlng, { - majAxis: feature.properties.items[1] / 3600.0, - minAxis: feature.properties.items[2] / 3600.0, - posAngle: - feature.properties.items[3] === '--' - ? 0.0 - : feature.properties.items[3], - }); - }, + service: 'Vizier@CDS', + name: 'GLEAM', + className: 'logo-catalog-vizier', + attribution: 'GaLactic and Extragalactic All-sky Murchison Wide Field Array (GLEAM)' + + ' low-frequency extragalactic catalogue (Hurley-Walker et al. 2017)', + color: 'blue', + maglim: 30.0, + regionType: 'box', + url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=VIII/100/gleamegc&' + + '-out=GLEAM,RAJ2000,DEJ2000,Fintwide,awide,bwide,pawide&-out.meta=&' + + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=-Fintwide', + properties: ['Fint(170-231MHz)', 'Major axis FWHM', 'Minor axis FWHM', 'Position angle'], + units: ['Jy', '″', '″', '°'], + objurl: L.Catalog.vizierURL + '/VizieR-5?-source=-source=VIII/100/gleamegc&-c={ra},{dec},eq=J2000&-c.rs=0.2', + draw: function (feature, latlng) { + return L.ellipse(latlng, { + majAxis: feature.properties.items[1] / 3600.0, + minAxis: feature.properties.items[2] / 3600.0, + posAngle: feature.properties.items[3] === '--' ? 0.0 : feature.properties.items[3] + }); + } }); L.Catalog.TGSS = L.extend({}, L.Catalog, { - service: 'Vizier@CDS', - name: 'TGSS', - className: 'logo-catalog-vizier', - attribution: - 'The GMRT 150 MHz all-sky radio survey. TGSS ADR1 (Intema et al. 2017)', - color: 'blue', - maglim: 30.0, - regionType: 'box', - url: - L.Catalog.vizierURL + - '/asu-tsv?&-mime=csv&-source=J/A%2bA/598/A78/table3&' + - '-out=TGSSADR,RAJ2000,DEJ2000,Stotal,Maj,Min,PA&-out.meta=&' + - '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=-Stotal', - properties: [ - 'Fpeak(150MHz)', - 'Major axis FWHM', - 'Minor axis FWHM', - 'Position angle', - ], - units: ['mJy', '″', '″', '°'], - objurl: - L.Catalog.vizierURL + - '/VizieR-3?-source=-source=J/A%2bA/598/A78/table3&-c={ra},{dec},eq=J2000&-c.rs=0.2', - draw: function (feature, latlng) { - return L.ellipse(latlng, { - majAxis: feature.properties.items[1] / 7200.0, - minAxis: feature.properties.items[2] / 7200.0, - posAngle: - feature.properties.items[3] === '--' - ? 0.0 - : feature.properties.items[3], - }); - }, + service: 'Vizier@CDS', + name: 'TGSS', + className: 'logo-catalog-vizier', + attribution: 'The GMRT 150 MHz all-sky radio survey. TGSS ADR1 (Intema et al. 2017)', + color: 'blue', + maglim: 30.0, + regionType: 'box', + url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=J/A%2bA/598/A78/table3&' + + '-out=TGSSADR,RAJ2000,DEJ2000,Stotal,Maj,Min,PA&-out.meta=&' + + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=-Stotal', + properties: ['Fpeak(150MHz)', 'Major axis FWHM', 'Minor axis FWHM', 'Position angle'], + units: ['mJy', '″', '″', '°'], + objurl: L.Catalog.vizierURL + '/VizieR-3?-source=-source=J/A%2bA/598/A78/table3&-c={ra},{dec},eq=J2000&-c.rs=0.2', + draw: function (feature, latlng) { + return L.ellipse(latlng, { + majAxis: feature.properties.items[1] / 7200.0, + minAxis: feature.properties.items[2] / 7200.0, + posAngle: feature.properties.items[3] === '--' ? 0.0 : feature.properties.items[3] + }); + } }); /* @@ -2698,60 +2290,60 @@ L.Catalog.TGSS = L.extend({}, L.Catalog, { */ L.FlipSwitch = L.Evented.extend({ - options: { - // All widget options - checked: false, - title: 'Click to switch', - className: 'leaflet-flipswitch', - id: 'leaflet-flipswitch', - }, - - initialize: function (parent, options) { - options = L.setOptions(this, options); - var _this = this, - className = options.className, - button = L.DomUtil.create('div', className, parent), - input = (this._input = L.DomUtil.create('input', className, button)), - label = L.DomUtil.create('label', className, button); - - input.type = 'checkbox'; - input.name = options.className; - input.checked = options.checked; - label.htmlFor = input.id = options.id; - if (options.title) { - label.title = options.title; - } - - L.DomUtil.create('span', className + '-inner', label); - L.DomUtil.create('span', className + '-button', label); - - L.DomEvent.disableClickPropagation(button).disableScrollPropagation(button); - L.DomEvent.on( - input, - 'change', - function () { - this.fire('change'); - }, - this - ); - - return button; - }, - - value: function (val) { - if (val === undefined) { - return this._input.checked; - } else { - this._input.checked = val ? true : false; - return this; - } - }, + options: { + // All widget options + checked: false, + title: 'Click to switch', + className: 'leaflet-flipswitch', + id: 'leaflet-flipswitch' + }, + + initialize: function (parent, options) { + options = L.setOptions(this, options); + var _this = this, + className = options.className, + button = L.DomUtil.create('div', className, parent), + input = this._input = L.DomUtil.create('input', className, button), + label = L.DomUtil.create('label', className, button); + + input.type = 'checkbox'; + input.name = options.className; + input.checked = options.checked; + label.htmlFor = input.id = options.id; + if (options.title) { + label.title = options.title; + } + + L.DomUtil.create('span', className + '-inner', label); + L.DomUtil.create('span', className + '-button', label); + + L.DomEvent + .disableClickPropagation(button) + .disableScrollPropagation(button); + L.DomEvent.on(input, 'change', function () { + this.fire('change'); + }, this); + + return button; + }, + + value: function (val) { + if (val === undefined) { + return this._input.checked; + } + else { + this._input.checked = val ? true : false; + return this; + } + } + }); L.flipswitch = function (parent, options) { - return new L.FlipSwitch(parent, options); + return new L.FlipSwitch(parent, options); }; + /* # SpinBox implements a number spinbox with adaptive step increment # Adapted from JTSage's spinbox (original attribution below), with all the @@ -2770,298 +2362,256 @@ L.flipswitch = function (parent, options) { */ L.SpinBox = L.Evented.extend({ - options: { - // All widget options - dmin: undefined, - dmax: undefined, - step: undefined, - initValue: undefined, - repButton: true, - clickEvent: 'click', - instantUpdate: false, - title: 'Enter value', - className: 'leaflet-spinbox', - }, - - initialize: function (parent, options) { - options = L.setOptions(this, options); - var _this = this, - drag = (this._drag = { - startEvent: 'touchstart mousedown', - stopEvent: 'touchend mouseup mouseout touchcancel', - move: false, - start: false, - end: false, - pos: false, - target: false, - delta: false, - tmp: false, - cnt: 0, - step: options.step, - prec: this._prec(options.step), - }), - wrap = (this._wrap = L.DomUtil.create('div', options.className, parent)), - input = (this._input = L.DomUtil.create( - 'input', - options.className + '-input', - wrap - )), - down = (this._down = L.DomUtil.create( - 'div', - options.className + '-down', - wrap - )), - up = (this._up = L.DomUtil.create( - 'div', - options.className + '-up', - wrap - )); - - input.type = 'number'; - input.step = 0.1; // Tells input that decimal numbers are valid - L.DomEvent.disableClickPropagation(wrap).disableScrollPropagation(wrap); - - if (input.disabled === true) { - options.disabled = true; - } - - if (options.dmin === undefined) { - options.dmin = -Number.MAX_VALUE; - } - if (options.dmax === undefined) { - options.dmax = Number.MAX_VALUE; - } - if (options.step === undefined) { - options.step = 1; - } - - if (options.initValue === undefined) { - options.initValue = (options.dmin + options.dmax) / 2.0; - } - - this.value(options.initValue); - - input.title = options.title; - down.title = 'Decrease number by ' + options.step; - up.title = 'Increase number by ' + options.step; - - L.DomEvent.on( - this._input, - 'change', - function () { - this.fire('change'); - }, - this - ); - - if (options.repButton === false) { - L.DomEvent.on( - down, - options.clickEvent, - function (e) { - e.preventDefault(); - this._offset(e.currentTarget, -1); - }, - this - ); - L.DomEvent.on( - up, - options.clickEvent, - function (e) { - e.preventDefault(); - this._offset(e.currentTarget, 1); - }, - this - ); - } else { - L.DomEvent.on( - down, - drag.startEvent, - function (e) { - input.blur(); - drag.move = true; - drag.cnt = 0; - drag.step = options.step; - drag.prec = this._prec(drag.step); - drag.delta = -1; - this._offset(e.currentTarget, -1); - if (!this.runButton) { - drag.target = e.currentTarget; - this.runButton = setTimeout(function () { - _this._sboxRun(); - }, 500); - } - }, - this - ); - L.DomEvent.on( - up, - drag.startEvent, - function (e) { - input.blur(); - drag.move = true; - drag.cnt = 0; - drag.step = options.step; - drag.prec = this._prec(drag.step); - drag.delta = 1; - this._offset(e.currentTarget, 1); - if (!this.runButton) { - drag.target = e.currentTarget; - this.runButton = setTimeout(function () { - _this._sboxRun(); - }, 500); - } - }, - this - ); - L.DomEvent.on( - down, - drag.stopEvent, - function (e) { - if (drag.move) { - e.preventDefault(); - clearTimeout(this.runButton); - this.runButton = false; - drag.move = false; - if (options.instantUpdate === false) { - this.fire('change'); - } - } - }, - this - ); - L.DomEvent.on( - up, - drag.stopEvent, - function (e) { - if (drag.move) { - e.preventDefault(); - clearTimeout(this.runButton); - this.runButton = false; - drag.move = false; - if (options.instantUpdate === false) { - this.fire('change'); - } - } - }, - this - ); - } - - if (options.disabled) { - this.disable(); - } - - return wrap; - }, - - value: function (val) { - if (val === undefined) { - return parseFloat(this._input.value); - } else { - this._input.value = val; - return this; - } - }, - - step: function (val) { - if (val === undefined) { - return this.options.step; - } else { - this.options.step = val; - return this; - } - }, - - disable: function () { - // Disable the element - var cname = 'disabled'; - - this._input.disabled = true; - this._input.blur(); - L.DomUtil.addClass(this._wrap, cname); - L.DomUtil.addClass(this._down, cname); - L.DomUtil.addClass(this._up, cname); - this.options.disabled = true; - }, - - enable: function () { - // Enable the element - var cname = 'disabled'; - - this._input.disabled = false; - L.DomUtil.removeClass(this._wrap, cname); - L.DomUtil.removeClass(this._down, cname); - L.DomUtil.removeClass(this._up, cname); - this.options.disabled = false; - }, - - _sboxRun: function () { - var _this = this, - timer = 150, - options = this.options, - drag = this._drag; - - if (drag.cnt === 20) { - timer = 50; - drag.step = 10.0 * options.step; - drag.prec = this._prec(drag.step); - } else if (drag.cnt === 40) { - timer = 10; - drag.step = 100.0 * options.step; - drag.prec = this._prec(drag.step); - } else if (drag.cnt === 60) { - drag.step = 1000.0 * options.step; - drag.prec = this._prec(drag.step); - } else if (drag.cnt === 80) { - drag.step = 10000.0 * options.step; - drag.prec = this._prec(drag.step); - } - drag.didRun = true; - this._offset(this, drag.delta); - drag.cnt++; - this.runButton = setTimeout(function () { - _this._sboxRun(); - }, timer); - }, - - _prec: function (step) { - var dprec = -0.4342944 * Math.log(step); - return dprec > 0.0 ? Math.ceil(dprec) : 0; - }, - - _offset: function (obj, direction) { - var tmp, - options = this.options, - input = this._input, - drag = this._drag; - - if (!this.disabled) { - if (direction < 1) { - tmp = (parseFloat(input.value) - drag.step).toFixed(drag.prec); - if (tmp >= options.dmin) { - input.value = tmp; - if (options.instantUpdate === true) { - this.fire('change'); - } - } - } else { - tmp = (parseFloat(input.value) + drag.step).toFixed(drag.prec); - if (tmp <= options.dmax) { - input.value = tmp; - if (options.instantUpdate === true) { - this.fire('change'); - } - } - } - } - }, + options: { + // All widget options + dmin: undefined, + dmax: undefined, + step: undefined, + initValue: undefined, + repButton: true, + clickEvent: 'click', + instantUpdate: false, + title: 'Enter value', + className: 'leaflet-spinbox' + }, + + initialize: function (parent, options) { + options = L.setOptions(this, options); + var _this = this, + drag = this._drag = { + startEvent: 'touchstart mousedown', + stopEvent: 'touchend mouseup mouseout touchcancel', + move: false, + start: false, + end: false, + pos: false, + target: false, + delta: false, + tmp: false, + cnt: 0, + step: options.step, + prec: this._prec(options.step) + }, + wrap = this._wrap = L.DomUtil.create('div', options.className, parent), + input = this._input = L.DomUtil.create('input', options.className + '-input', wrap), + down = this._down = L.DomUtil.create('div', options.className + '-down', wrap), + up = this._up = L.DomUtil.create('div', options.className + '-up', wrap); + + input.type = 'number'; + input.step = 0.1; // Tells input that decimal numbers are valid + L.DomEvent + .disableClickPropagation(wrap) + .disableScrollPropagation(wrap); + + if (input.disabled === true) { + options.disabled = true; + } + + if (options.dmin === undefined) { + options.dmin = - Number.MAX_VALUE; + } + if (options.dmax === undefined) { + options.dmax = Number.MAX_VALUE; + } + if (options.step === undefined) { + options.step = 1; + } + + if (options.initValue === undefined) { + options.initValue = (options.dmin + options.dmax) / 2.0; + } + + this.value(options.initValue); + + input.title = options.title; + down.title = 'Decrease number by ' + options.step; + up.title = 'Increase number by ' + options.step; + + L.DomEvent.on(this._input, 'change', function () { + this.fire('change'); + }, this); + + if (options.repButton === false) { + L.DomEvent.on(down, options.clickEvent, function (e) { + e.preventDefault(); + this._offset(e.currentTarget, -1); + }, this); + L.DomEvent.on(up, options.clickEvent, function (e) { + e.preventDefault(); + this._offset(e.currentTarget, 1); + }, this); + } else { + L.DomEvent.on(down, drag.startEvent, function (e) { + input.blur(); + drag.move = true; + drag.cnt = 0; + drag.step = options.step; + drag.prec = this._prec(drag.step); + drag.delta = -1; + this._offset(e.currentTarget, -1); + if (!this.runButton) { + drag.target = e.currentTarget; + this.runButton = setTimeout(function () { + _this._sboxRun(); + }, 500); + } + }, this); + L.DomEvent.on(up, drag.startEvent, function (e) { + input.blur(); + drag.move = true; + drag.cnt = 0; + drag.step = options.step; + drag.prec = this._prec(drag.step); + drag.delta = 1; + this._offset(e.currentTarget, 1); + if (!this.runButton) { + drag.target = e.currentTarget; + this.runButton = setTimeout(function () { + _this._sboxRun(); + }, 500); + } + }, this); + L.DomEvent.on(down, drag.stopEvent, function (e) { + if (drag.move) { + e.preventDefault(); + clearTimeout(this.runButton); + this.runButton = false; + drag.move = false; + if (options.instantUpdate === false) { + this.fire('change'); + } + } + }, this); + L.DomEvent.on(up, drag.stopEvent, function (e) { + if (drag.move) { + e.preventDefault(); + clearTimeout(this.runButton); + this.runButton = false; + drag.move = false; + if (options.instantUpdate === false) { + this.fire('change'); + } + } + }, this); + } + + if (options.disabled) { + this.disable(); + } + + return wrap; + }, + + value: function (val) { + if (val === undefined) { + return parseFloat(this._input.value); + } + else { + this._input.value = val; + return this; + } + }, + + step: function (val) { + if (val === undefined) { + return this.options.step; + } + else { + this.options.step = val; + return this; + } + }, + + disable: function () { + // Disable the element + var cname = 'disabled'; + + this._input.disabled = true; + this._input.blur(); + L.DomUtil.addClass(this._wrap, cname); + L.DomUtil.addClass(this._down, cname); + L.DomUtil.addClass(this._up, cname); + this.options.disabled = true; + }, + + enable: function () { + // Enable the element + var cname = 'disabled'; + + this._input.disabled = false; + L.DomUtil.removeClass(this._wrap, cname); + L.DomUtil.removeClass(this._down, cname); + L.DomUtil.removeClass(this._up, cname); + this.options.disabled = false; + }, + + _sboxRun: function () { + var _this = this, + timer = 150, + options = this.options, + drag = this._drag; + + if (drag.cnt === 20) { + timer = 50; + drag.step = 10.0 * options.step; + drag.prec = this._prec(drag.step); + } else if (drag.cnt === 40) { + timer = 10; + drag.step = 100.0 * options.step; + drag.prec = this._prec(drag.step); + } else if (drag.cnt === 60) { + drag.step = 1000.0 * options.step; + drag.prec = this._prec(drag.step); + } else if (drag.cnt === 80) { + drag.step = 10000.0 * options.step; + drag.prec = this._prec(drag.step); + } + drag.didRun = true; + this._offset(this, drag.delta); + drag.cnt++; + this.runButton = setTimeout(function () { + _this._sboxRun(); + }, timer); + }, + + _prec: function (step) { + var dprec = -0.4342944 * Math.log(step); + return dprec > 0.0 ? Math.ceil(dprec) : 0; + }, + + _offset: function (obj, direction) { + var tmp, + options = this.options, + input = this._input, + drag = this._drag; + + if (!this.disabled) { + if (direction < 1) { + tmp = (parseFloat(input.value) - drag.step).toFixed(drag.prec); + if (tmp >= options.dmin) { + input.value = tmp; + if (options.instantUpdate === true) { + this.fire('change'); + } + } + } else { + tmp = (parseFloat(input.value) + drag.step).toFixed(drag.prec); + if (tmp <= options.dmax) { + input.value = tmp; + if (options.instantUpdate === true) { + this.fire('change'); + } + } + } + } + } }); L.spinbox = function (parent, options) { - return new L.SpinBox(parent, options); + return new L.SpinBox(parent, options); }; + /* # FileTree parses directory trees serverside. # Adapted from the jQuery File Tree Plugin (original copyright notice reproduced @@ -3097,154 +2647,87 @@ L.spinbox = function (parent, options) { */ if (typeof require !== 'undefined') { - var $ = require('jquery-browser'); + var $ = require('jquery-browser'); } $.extend($.fn, { - fileTree: function (options, file) { - // Default options - if (options.root === undefined) { - options.root = '/'; - } - if (options.script === undefined) { - options.script = 'dist/filetree.php'; - } - if (options.folderEvent === undefined) { - options.folderEvent = 'click'; - } - if (options.expandSpeed === undefined) { - options.expandSpeed = 500; - } - if (options.collapseSpeed === undefined) { - options.collapseSpeed = 500; - } - if (options.expandEasing === undefined) { - options.expandEasing = null; - } - if (options.collapseEasing === undefined) { - options.collapseEasing = null; - } - if (options.multiFolder === undefined) { - options.multiFolder = true; - } - if (options.loadMessage === undefined) { - options.loadMessage = 'Loading...'; - } - - $(this).each(function () { - function showTree(element, dir) { - $(element).addClass('wait'); - $('.filetree.start').remove(); - $.post(options.script, { dir: dir }, function (data) { - $(element) - .find('.start') - .html(''); - $(element) - .removeClass('wait') - .append(data); - if (options.root === dir) { - $(element) - .find('UL:hidden') - .show(); - } else { - $(element) - .find('UL:hidden') - .slideDown({ - duration: options.expandSpeed, - easing: options.expandEasing, - }); - } - bindTree(element); - }); - } - - function bindTree(element) { - $(element) - .find('LI A') - .on(options.folderEvent, function () { - if ( - $(this) - .parent() - .hasClass('directory') - ) { - if ( - $(this) - .parent() - .hasClass('collapsed') - ) { - // Expand - if (!options.multiFolder) { - $(this) - .parent() - .parent() - .find('UL') - .slideUp({ - duration: options.collapseSpeed, - easing: options.collapseEasing, - }); - $(this) - .parent() - .parent() - .find('LI.directory') - .removeClass('expanded') - .addClass('collapsed'); - } - $(this) - .parent() - .find('UL') - .remove(); // cleanup - showTree( - $(this).parent(), - encodeURI( - $(this) - .attr('rel') - .match(/.*\//) - ) - ); - $(this) - .parent() - .removeClass('collapsed') - .addClass('expanded'); - } else { - // Collapse - $(this) - .parent() - .find('UL') - .slideUp({ - duration: options.collapseSpeed, - easing: options.collapseEasing, - }); - $(this) - .parent() - .removeClass('expanded') - .addClass('collapsed'); - } - } else { - file($(this).attr('rel')); - } - return false; - }); - // Prevent A from triggering the # on non-click events - if (options.folderEvent.toLowerCase !== 'click') { - $(element) - .find('LI A') - .on('click', function () { - return false; - }); - } - } - // Loading message - $(this).html( - '
  • ' + - options.loadMessage + - '
' - ); - // Get the initial file list - showTree($(this), encodeURI(options.root)); - }); - }, + fileTree: function (options, file) { + // Default options + if (options.root === undefined) { options.root = '/'; } + if (options.script === undefined) { options.script = 'dist/filetree.php'; } + if (options.folderEvent === undefined) { options.folderEvent = 'click'; } + if (options.expandSpeed === undefined) { options.expandSpeed = 500; } + if (options.collapseSpeed === undefined) { options.collapseSpeed = 500; } + if (options.expandEasing === undefined) { options.expandEasing = null; } + if (options.collapseEasing === undefined) { options.collapseEasing = null; } + if (options.multiFolder === undefined) { options.multiFolder = true; } + if (options.loadMessage === undefined) { options.loadMessage = 'Loading...'; } + + $(this).each(function () { + + function showTree(element, dir) { + $(element).addClass('wait'); + $('.filetree.start').remove(); + $.post(options.script, { dir: dir }, function (data) { + $(element).find('.start').html(''); + $(element).removeClass('wait').append(data); + if (options.root === dir) { + $(element).find('UL:hidden').show(); + } else { + $(element).find('UL:hidden').slideDown({ + duration: options.expandSpeed, + easing: options.expandEasing + }); + } + bindTree(element); + }); + } + + function bindTree(element) { + $(element).find('LI A').on(options.folderEvent, function () { + if ($(this).parent().hasClass('directory')) { + if ($(this).parent().hasClass('collapsed')) { + // Expand + if (!options.multiFolder) { + $(this).parent().parent().find('UL').slideUp({ + duration: options.collapseSpeed, + easing: options.collapseEasing + }); + $(this).parent().parent().find('LI.directory') + .removeClass('expanded') + .addClass('collapsed'); + } + $(this).parent().find('UL').remove(); // cleanup + showTree($(this).parent(), encodeURI($(this).attr('rel').match(/.*\//))); + $(this).parent().removeClass('collapsed').addClass('expanded'); + } else { + // Collapse + $(this).parent().find('UL').slideUp({ + duration: options.collapseSpeed, + easing: options.collapseEasing + }); + $(this).parent().removeClass('expanded').addClass('collapsed'); + } + } else { + file($(this).attr('rel')); + } + return false; + }); + // Prevent A from triggering the # on non-click events + if (options.folderEvent.toLowerCase !== 'click') { + $(element).find('LI A').on('click', function () { return false; }); + } + } + // Loading message + $(this).html('
  • ' + options.loadMessage + '
'); + // Get the initial file list + showTree($(this), encodeURI(options.root)); + }); + } }); + + /* # L.Control.Attribution.Logos adds a VisiOmatic logo to the map. # @@ -3258,49 +2741,48 @@ $.extend($.fn, { // Remove this ugly Pipe sign L.Control.Attribution.include({ - _update: function () { - if (!this._map) { - return; - } - - var attribs = []; - - for (var i in this._attributions) { - if (this._attributions[i]) { - attribs.push(i); - } - } - - var prefixAndAttribs = []; - - if (this.options.prefix) { - prefixAndAttribs.push(this.options.prefix); - } - if (attribs.length) { - prefixAndAttribs.push(attribs.join(', ')); - } - - this._container.innerHTML = prefixAndAttribs.join(' © '); - }, + _update: function () { + if (!this._map) { return; } + + var attribs = []; + + for (var i in this._attributions) { + if (this._attributions[i]) { + attribs.push(i); + } + } + + var prefixAndAttribs = []; + + if (this.options.prefix) { + prefixAndAttribs.push(this.options.prefix); + } + if (attribs.length) { + prefixAndAttribs.push(attribs.join(', ')); + } + + this._container.innerHTML = prefixAndAttribs.join(' © '); + } }); // Set Attribution prefix to a series of clickable logos L.Map.addInitHook(function () { - if ( - this.options.visiomaticLogo !== false && - this.options.attributionControl - ) { - this.attributionControl.setPrefix( - '' + - '' + - '' - ); - } + if (this.options.visiomaticLogo !== false && + this.options.attributionControl) { + this.attributionControl.setPrefix( + '' + + '' + + '' + ); + } }); + + + /* # L.Control.ExtraMap adds support for extra synchronized maps # (Picture-in-Picture style). Adapted from L.Control.MiniMap by Norkart @@ -3336,363 +2818,323 @@ TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF TH SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ L.Control.ExtraMap = L.Control.extend({ - options: { - position: 'bottomright', - title: 'Navigation mini-map. Grab to navigate', - toggleDisplay: true, - zoomLevelFixed: false, - zoomLevelOffset: -5, - zoomAnimation: false, - autoToggleDisplay: false, - width: 150, - height: 150, - collapsedWidth: 24, - collapsedHeight: 24, - aimingRectOptions: { - color: '#FFFFFF', - weight: 1, - clickable: false, - }, - shadowRectOptions: { - color: '#FDC82F', - weight: 1, - clickable: false, - opacity: 0, - fillOpacity: 0, - }, - strings: { hideText: 'Hide map', showText: 'Show map' }, - }, - - // Layer is the map layer to be shown in the minimap - initialize: function (layer, options) { - L.Util.setOptions(this, options); - // Make sure the aiming rects are non-clickable even if the user tries to set - // them clickable (most likely by forgetting to specify them false) - this.options.aimingRectOptions.clickable = false; - this.options.shadowRectOptions.clickable = false; - this._layer = layer; - }, - - onAdd: function (map) { - this._mainMap = map; - - // Creating the container and stopping events from spilling through to the main map. - this._container = L.DomUtil.create('div', 'leaflet-control-extramap'); - this._container.style.width = this.options.width + 'px'; - this._container.style.height = this.options.height + 'px'; - this._container.title = this.options.title; - L.DomEvent.disableClickPropagation(this._container); - L.DomEvent.on(this._container, 'mousewheel', L.DomEvent.stopPropagation); - - this._extraMap = new L.Map(this._container, { - attributionControl: false, - zoomControl: false, - zoomAnimation: this.options.zoomAnimation, - autoToggleDisplay: this.options.autoToggleDisplay, - touchZoom: !this._isZoomLevelFixed(), - scrollWheelZoom: !this._isZoomLevelFixed(), - doubleClickZoom: !this._isZoomLevelFixed(), - boxZoom: !this._isZoomLevelFixed(), - }); - - this._layer.addTo(this._extraMap); - - // These bools are used to prevent infinite loops of the two maps notifying - // each other that they've moved. - // this._mainMapMoving = false; - // this._extraMapMoving = false; - - //Keep a record of this to prevent auto toggling when the user explicitly doesn't want it. - this._userToggledDisplay = false; - this._minimized = false; - - if (this.options.toggleDisplay) { - this._addToggleButton(); - } - - this._layer.once( - 'metaload', - function () { - this._mainMap.whenReady( - L.Util.bind(function () { - this._extraMap.whenReady( - L.Util.bind(function () { - this._aimingRect = L.rectangle( - this._mainMap.getBounds(), - this.options.aimingRectOptions - ).addTo(this._extraMap); - this._shadowRect = L.rectangle( - this._mainMap.getBounds(), - this.options.shadowRectOptions - ).addTo(this._extraMap); - this._mainMap.on('moveend', this._onMainMapMoved, this); - this._mainMap.on('move', this._onMainMapMoving, this); - this._extraMap.on( - 'movestart', - this._onExtraMapMoveStarted, - this - ); - this._extraMap.on('move', this._onExtraMapMoving, this); - this._extraMap.on('moveend', this._onExtraMapMoved, this); - this._extraMap.setView( - this._mainMap.getCenter(), - this._decideZoom(true) - ); - this._setDisplay(this._decideMinimized()); - }, this) - ); - }, this) - ); - }, - this - ); - - return this._container; - }, - - addTo: function (map) { - L.Control.prototype.addTo.call(this, map); - return this; - }, - - onRemove: function (map) { - this._mainMap.off('moveend', this._onMainMapMoved, this); - this._mainMap.off('move', this._onMainMapMoving, this); - this._extraMap.off('moveend', this._onExtraMapMoved, this); - - this._extraMap.removeLayer(this._layer); - }, - - changeLayer: function (layer) { - this._extraMap.removeLayer(this._layer); - this._layer = layer; - this._extraMap.addLayer(this._layer); - }, - - _addToggleButton: function () { - this._toggleDisplayButton = this.options.toggleDisplay - ? this._createButton( - '', - this.options.strings.hideText, - 'leaflet-control-extramap-toggle-display ' + - 'leaflet-control-extramap-toggle-display-' + - this.options.position, - this._container, - this._toggleDisplayButtonClicked, - this - ) - : undefined; - - this._toggleDisplayButton.style.width = this.options.collapsedWidth + 'px'; - this._toggleDisplayButton.style.height = - this.options.collapsedHeight + 'px'; - }, - - _createButton: function (html, title, className, container, fn, context) { - var link = L.DomUtil.create('a', className, container); - link.innerHTML = html; - link.href = '#'; - link.title = title; - - var stop = L.DomEvent.stopPropagation; - - L.DomEvent.on(link, 'click', stop) - .on(link, 'mousedown', stop) - .on(link, 'dblclick', stop) - .on(link, 'click', L.DomEvent.preventDefault) - .on(link, 'click', fn, context); - - return link; - }, - - _toggleDisplayButtonClicked: function () { - this._userToggledDisplay = true; - if (!this._minimized) { - this._minimize(); - this._toggleDisplayButton.title = this.options.strings.showText; - } else { - this._restore(); - this._toggleDisplayButton.title = this.options.strings.hideText; - } - }, - - _setDisplay: function (minimize) { - if (minimize !== this._minimized) { - if (!this._minimized) { - this._minimize(); - } else { - this._restore(); - } - } - }, - - _minimize: function () { - // hide the minimap - if (this.options.toggleDisplay) { - this._container.style.width = this.options.collapsedWidth + 'px'; - this._container.style.height = this.options.collapsedHeight + 'px'; - this._toggleDisplayButton.className += - ' minimized-' + this.options.position; - } else { - this._container.style.display = 'none'; - } - this._minimized = true; - }, - - _restore: function () { - if (this.options.toggleDisplay) { - this._container.style.width = this.options.width + 'px'; - this._container.style.height = this.options.height + 'px'; - this._toggleDisplayButton.className = this._toggleDisplayButton.className.replace( - 'minimized-' + this.options.position, - '' - ); - } else { - this._container.style.display = 'block'; - } - this._minimized = false; - }, - - _onMainMapMoved: function (e) { - if (!this._extraMapMoving) { - this._mainMapMoving = true; - this._extraMap.setView(this._mainMap.getCenter(), this._decideZoom(true)); - this._setDisplay(this._decideMinimized()); - } else { - this._extraMapMoving = false; - } - this._aimingRect.setBounds(this._mainMap.getBounds()); - }, - - _onMainMapMoving: function (e) { - this._aimingRect.setBounds(this._mainMap.getBounds()); - }, - - _onExtraMapMoveStarted: function (e) { - var lastAimingRect = this._aimingRect.getBounds(); - var sw = this._extraMap.latLngToContainerPoint( - lastAimingRect.getSouthWest() - ); - var ne = this._extraMap.latLngToContainerPoint( - lastAimingRect.getNorthEast() - ); - this._lastAimingRectPosition = { sw: sw, ne: ne }; - }, - - _onExtraMapMoving: function (e) { - if (!this._mainMapMoving && this._lastAimingRectPosition) { - this._shadowRect.setBounds( - new L.LatLngBounds( - this._extraMap.containerPointToLatLng( - this._lastAimingRectPosition.sw - ), - this._extraMap.containerPointToLatLng(this._lastAimingRectPosition.ne) - ) - ); - this._shadowRect.setStyle({ opacity: 1, fillOpacity: 0.3 }); - } - }, - - _onExtraMapMoved: function (e) { - if (!this._mainMapMoving) { - this._extraMapMoving = true; - this._mainMap.setView( - this._extraMap.getCenter(), - this._decideZoom(false) - ); - this._shadowRect.setStyle({ opacity: 0, fillOpacity: 0 }); - } else { - this._mainMapMoving = false; - } - }, - - _isZoomLevelFixed: function () { - var zoomLevelFixed = this.options.zoomLevelFixed; - return this._isDefined(zoomLevelFixed) && this._isInteger(zoomLevelFixed); - }, - - _decideZoom: function (fromMaintoExtra) { - if (!this._isZoomLevelFixed()) { - if (fromMaintoExtra) { - return this._mainMap.getZoom() + this.options.zoomLevelOffset; - } else { - var currentDiff = this._extraMap.getZoom() - this._mainMap.getZoom(); - var proposedZoom = - this._extraMap.getZoom() - this.options.zoomLevelOffset; - var toRet; - - if ( - currentDiff > this.options.zoomLevelOffset && - this._mainMap.getZoom() < - this._extraMap.getMinZoom() - this.options.zoomLevelOffset - ) { - // This means the extraMap is zoomed out to the minimum zoom level and - // can't zoom any more. - if (this._extraMap.getZoom() > this._lastExtraMapZoom) { - // This means the user is trying to zoom in by using the minimap, zoom the main map. - toRet = this._mainMap.getZoom() + 1; - // Also we cheat and zoom the minimap out again to keep it visually consistent. - this._extraMap.setZoom(this._extraMap.getZoom() - 1); - } else { - // Either the user is trying to zoom out past the minimap's min zoom or - // has just panned using it, we can't tell the difference. Therefore, we ignore it! - toRet = this._mainMap.getZoom(); - } - } else { - // This is what happens in the majority of cases, and always if you - // configure the min levels + offset in a sane fashion. - toRet = proposedZoom; - } - this._lastExtraMapZoom = this._extraMap.getZoom(); - return toRet; - } - } else { - if (fromMaintoExtra) { - return this.options.zoomLevelFixed; - } else { - return this._mainMap.getZoom(); - } - } - }, - - _decideMinimized: function () { - if (this._userToggledDisplay) { - return this._minimized; - } - - if (this.options.autoToggleDisplay) { - if (this._mainMap.getBounds().contains(this._extraMap.getBounds())) { - return true; - } - return false; - } - - return this._minimized; - }, - - _isInteger: function (value) { - return typeof value === 'number'; - }, - - _isDefined: function (value) { - return typeof value !== 'undefined'; - }, + options: { + position: 'bottomright', + title: 'Navigation mini-map. Grab to navigate', + toggleDisplay: true, + zoomLevelFixed: false, + zoomLevelOffset: -5, + zoomAnimation: false, + autoToggleDisplay: false, + width: 150, + height: 150, + collapsedWidth: 24, + collapsedHeight: 24, + aimingRectOptions: { + color: '#FFFFFF', + weight: 1, + clickable: false + }, + shadowRectOptions: { + color: '#FDC82F', + weight: 1, + clickable: false, + opacity: 0, + fillOpacity: 0 + }, + strings: { hideText: 'Hide map', showText: 'Show map' } + }, + + // Layer is the map layer to be shown in the minimap + initialize: function (layer, options) { + L.Util.setOptions(this, options); + // Make sure the aiming rects are non-clickable even if the user tries to set + // them clickable (most likely by forgetting to specify them false) + this.options.aimingRectOptions.clickable = false; + this.options.shadowRectOptions.clickable = false; + this._layer = layer; + }, + + onAdd: function (map) { + + this._mainMap = map; + + // Creating the container and stopping events from spilling through to the main map. + this._container = L.DomUtil.create('div', 'leaflet-control-extramap'); + this._container.style.width = this.options.width + 'px'; + this._container.style.height = this.options.height + 'px'; + this._container.title = this.options.title; + L.DomEvent.disableClickPropagation(this._container); + L.DomEvent.on(this._container, 'mousewheel', L.DomEvent.stopPropagation); + + this._extraMap = new L.Map(this._container, { + attributionControl: false, + zoomControl: false, + zoomAnimation: this.options.zoomAnimation, + autoToggleDisplay: this.options.autoToggleDisplay, + touchZoom: !this._isZoomLevelFixed(), + scrollWheelZoom: !this._isZoomLevelFixed(), + doubleClickZoom: !this._isZoomLevelFixed(), + boxZoom: !this._isZoomLevelFixed() + }); + + this._layer.addTo(this._extraMap); + + // These bools are used to prevent infinite loops of the two maps notifying + // each other that they've moved. + // this._mainMapMoving = false; + // this._extraMapMoving = false; + + //Keep a record of this to prevent auto toggling when the user explicitly doesn't want it. + this._userToggledDisplay = false; + this._minimized = false; + + if (this.options.toggleDisplay) { + this._addToggleButton(); + } + + this._layer.once('metaload', function () { + this._mainMap.whenReady(L.Util.bind(function () { + this._extraMap.whenReady(L.Util.bind(function () { + this._aimingRect = L.rectangle(this._mainMap.getBounds(), + this.options.aimingRectOptions).addTo(this._extraMap); + this._shadowRect = L.rectangle(this._mainMap.getBounds(), + this.options.shadowRectOptions).addTo(this._extraMap); + this._mainMap.on('moveend', this._onMainMapMoved, this); + this._mainMap.on('move', this._onMainMapMoving, this); + this._extraMap.on('movestart', this._onExtraMapMoveStarted, this); + this._extraMap.on('move', this._onExtraMapMoving, this); + this._extraMap.on('moveend', this._onExtraMapMoved, this); + this._extraMap.setView(this._mainMap.getCenter(), this._decideZoom(true)); + this._setDisplay(this._decideMinimized()); + }, this)); + }, this)); + }, this); + + return this._container; + }, + + addTo: function (map) { + L.Control.prototype.addTo.call(this, map); + return this; + }, + + onRemove: function (map) { + this._mainMap.off('moveend', this._onMainMapMoved, this); + this._mainMap.off('move', this._onMainMapMoving, this); + this._extraMap.off('moveend', this._onExtraMapMoved, this); + + this._extraMap.removeLayer(this._layer); + }, + + changeLayer: function (layer) { + this._extraMap.removeLayer(this._layer); + this._layer = layer; + this._extraMap.addLayer(this._layer); + }, + + _addToggleButton: function () { + this._toggleDisplayButton = this.options.toggleDisplay ? this._createButton( + '', this.options.strings.hideText, ( + 'leaflet-control-extramap-toggle-display ' + + 'leaflet-control-extramap-toggle-display-' + this.options.position + ), + this._container, this._toggleDisplayButtonClicked, this + ) : undefined; + + this._toggleDisplayButton.style.width = this.options.collapsedWidth + 'px'; + this._toggleDisplayButton.style.height = this.options.collapsedHeight + 'px'; + }, + + _createButton: function (html, title, className, container, fn, context) { + var link = L.DomUtil.create('a', className, container); + link.innerHTML = html; + link.href = '#'; + link.title = title; + + var stop = L.DomEvent.stopPropagation; + + L.DomEvent + .on(link, 'click', stop) + .on(link, 'mousedown', stop) + .on(link, 'dblclick', stop) + .on(link, 'click', L.DomEvent.preventDefault) + .on(link, 'click', fn, context); + + return link; + }, + + _toggleDisplayButtonClicked: function () { + this._userToggledDisplay = true; + if (!this._minimized) { + this._minimize(); + this._toggleDisplayButton.title = this.options.strings.showText; + } else { + this._restore(); + this._toggleDisplayButton.title = this.options.strings.hideText; + } + }, + + _setDisplay: function (minimize) { + if (minimize !== this._minimized) { + if (!this._minimized) { + this._minimize(); + } else { + this._restore(); + } + } + }, + + _minimize: function () { + // hide the minimap + if (this.options.toggleDisplay) { + this._container.style.width = this.options.collapsedWidth + 'px'; + this._container.style.height = this.options.collapsedHeight + 'px'; + this._toggleDisplayButton.className += (' minimized-' + this.options.position); + } else { + this._container.style.display = 'none'; + } + this._minimized = true; + }, + + _restore: function () { + if (this.options.toggleDisplay) { + this._container.style.width = this.options.width + 'px'; + this._container.style.height = this.options.height + 'px'; + this._toggleDisplayButton.className = this._toggleDisplayButton.className + .replace('minimized-' + this.options.position, ''); + } else { + this._container.style.display = 'block'; + } + this._minimized = false; + }, + + _onMainMapMoved: function (e) { + if (!this._extraMapMoving) { + this._mainMapMoving = true; + this._extraMap.setView(this._mainMap.getCenter(), this._decideZoom(true)); + this._setDisplay(this._decideMinimized()); + } else { + this._extraMapMoving = false; + } + this._aimingRect.setBounds(this._mainMap.getBounds()); + }, + + _onMainMapMoving: function (e) { + this._aimingRect.setBounds(this._mainMap.getBounds()); + }, + + _onExtraMapMoveStarted: function (e) { + var lastAimingRect = this._aimingRect.getBounds(); + var sw = this._extraMap.latLngToContainerPoint(lastAimingRect.getSouthWest()); + var ne = this._extraMap.latLngToContainerPoint(lastAimingRect.getNorthEast()); + this._lastAimingRectPosition = { sw: sw, ne: ne }; + }, + + _onExtraMapMoving: function (e) { + if (!this._mainMapMoving && this._lastAimingRectPosition) { + this._shadowRect.setBounds(new L.LatLngBounds( + this._extraMap.containerPointToLatLng(this._lastAimingRectPosition.sw), + this._extraMap.containerPointToLatLng(this._lastAimingRectPosition.ne) + )); + this._shadowRect.setStyle({ opacity: 1, fillOpacity: 0.3 }); + } + }, + + _onExtraMapMoved: function (e) { + if (!this._mainMapMoving) { + this._extraMapMoving = true; + this._mainMap.setView(this._extraMap.getCenter(), this._decideZoom(false)); + this._shadowRect.setStyle({ opacity: 0, fillOpacity: 0 }); + } else { + this._mainMapMoving = false; + } + }, + + _isZoomLevelFixed: function () { + var zoomLevelFixed = this.options.zoomLevelFixed; + return this._isDefined(zoomLevelFixed) && this._isInteger(zoomLevelFixed); + }, + + _decideZoom: function (fromMaintoExtra) { + if (!this._isZoomLevelFixed()) { + if (fromMaintoExtra) { + return this._mainMap.getZoom() + this.options.zoomLevelOffset; + } else { + var currentDiff = this._extraMap.getZoom() - this._mainMap.getZoom(); + var proposedZoom = this._extraMap.getZoom() - this.options.zoomLevelOffset; + var toRet; + + if (currentDiff > this.options.zoomLevelOffset && + this._mainMap.getZoom() < this._extraMap.getMinZoom() - this.options.zoomLevelOffset) { + // This means the extraMap is zoomed out to the minimum zoom level and + // can't zoom any more. + if (this._extraMap.getZoom() > this._lastExtraMapZoom) { + // This means the user is trying to zoom in by using the minimap, zoom the main map. + toRet = this._mainMap.getZoom() + 1; + // Also we cheat and zoom the minimap out again to keep it visually consistent. + this._extraMap.setZoom(this._extraMap.getZoom() - 1); + } else { + // Either the user is trying to zoom out past the minimap's min zoom or + // has just panned using it, we can't tell the difference. Therefore, we ignore it! + toRet = this._mainMap.getZoom(); + } + } else { + // This is what happens in the majority of cases, and always if you + // configure the min levels + offset in a sane fashion. + toRet = proposedZoom; + } + this._lastExtraMapZoom = this._extraMap.getZoom(); + return toRet; + } + } else { + if (fromMaintoExtra) { + return this.options.zoomLevelFixed; + } else { + return this._mainMap.getZoom(); + } + } + }, + + _decideMinimized: function () { + if (this._userToggledDisplay) { + return this._minimized; + } + + if (this.options.autoToggleDisplay) { + if (this._mainMap.getBounds().contains(this._extraMap.getBounds())) { + return true; + } + return false; + } + + return this._minimized; + }, + + _isInteger: function (value) { + return typeof value === 'number'; + }, + + _isDefined: function (value) { + return typeof value !== 'undefined'; + } }); L.Map.mergeOptions({ - extraMapControl: false, + extraMapControl: false }); L.Map.addInitHook(function () { - if (this.options.extraMapControl) { - this.extraMapControl = new L.Control.ExtraMap().addTo(this); - } + if (this.options.extraMapControl) { + this.extraMapControl = (new L.Control.ExtraMap()).addTo(this); + } }); L.control.extraMap = function (layer, options) { - return new L.Control.ExtraMap(layer, options); + return new L.Control.ExtraMap(layer, options); }; + /* # L.Control.FullScreen adds a full screen toggle button to the map. # Adapted from the leaflet.fullscreen plugin by Bruno Bergot (fixed jake errors) @@ -3730,211 +3172,172 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ if (typeof require !== 'undefined') { - var jQuery = require('jquery-browser'); + var jQuery = require('jquery-browser'); } + (function () { - L.Control.FullScreen = L.Control.extend({ - options: { - position: 'topleft', - title: 'Toggle full screen mode', - forceSeparateButton: false, - }, - - onAdd: function (map) { - var className = 'leaflet-control-zoom-fullscreen', - container; - - if (map.zoomControl && !this.options.forceSeparateButton) { - container = map.zoomControl._container; - } else { - container = L.DomUtil.create('div', 'leaflet-bar'); - } - - this._createButton( - this.options.title, - className, - container, - this.toogleFullScreen, - map - ); - - return container; - }, - - _createButton: function (title, className, container, fn, context) { - var link = L.DomUtil.create('a', className, container); - link.href = '#'; - link.title = title; - - L.DomEvent.addListener(link, 'click', L.DomEvent.stopPropagation) - .addListener(link, 'click', L.DomEvent.preventDefault) - .addListener(link, 'click', fn, context); - - L.DomEvent.addListener( - container, - fullScreenApi.fullScreenEventName, - L.DomEvent.stopPropagation - ) - .addListener( - container, - fullScreenApi.fullScreenEventName, - L.DomEvent.preventDefault - ) - .addListener( - container, - fullScreenApi.fullScreenEventName, - this._handleEscKey, - context - ); - - L.DomEvent.addListener( - document, - fullScreenApi.fullScreenEventName, - L.DomEvent.stopPropagation - ) - .addListener( - document, - fullScreenApi.fullScreenEventName, - L.DomEvent.preventDefault - ) - .addListener( - document, - fullScreenApi.fullScreenEventName, - this._handleEscKey, - context - ); - - return link; - }, - - toogleFullScreen: function () { - this._exitFired = false; - var container = this._container; - if (this._isFullscreen) { - if (fullScreenApi.supportsFullScreen) { - fullScreenApi.cancelFullScreen(container); - } else { - L.DomUtil.removeClass(container, 'leaflet-pseudo-fullscreen'); - } - this.invalidateSize(); - this.fire('exitFullscreen'); - this._exitFired = true; - this._isFullscreen = false; - } else { - if (fullScreenApi.supportsFullScreen) { - fullScreenApi.requestFullScreen(container); - } else { - L.DomUtil.addClass(container, 'leaflet-pseudo-fullscreen'); - } - this.invalidateSize(); - this.fire('enterFullscreen'); - this._isFullscreen = true; - } - }, - - _handleEscKey: function () { - if (!fullScreenApi.isFullScreen(this) && !this._exitFired) { - this.fire('exitFullscreen'); - this._exitFired = true; - this._isFullscreen = false; - } - }, - }); - - L.Map.addInitHook(function () { - if (this.options.fullscreenControl) { - this.fullscreenControl = L.control.fullscreen( - this.options.fullscreenControlOptions - ); - this.addControl(this.fullscreenControl); - } - }); - - L.control.fullscreen = function (options) { - return new L.Control.FullScreen(options); - }; - - /* + + L.Control.FullScreen = L.Control.extend({ + options: { + position: 'topleft', + title: 'Toggle full screen mode', + forceSeparateButton: false + }, + + onAdd: function (map) { + var className = 'leaflet-control-zoom-fullscreen', container; + + if (map.zoomControl && !this.options.forceSeparateButton) { + container = map.zoomControl._container; + } else { + container = L.DomUtil.create('div', 'leaflet-bar'); + } + + this._createButton(this.options.title, className, container, this.toogleFullScreen, map); + + return container; + }, + + _createButton: function (title, className, container, fn, context) { + var link = L.DomUtil.create('a', className, container); + link.href = '#'; + link.title = title; + + L.DomEvent + .addListener(link, 'click', L.DomEvent.stopPropagation) + .addListener(link, 'click', L.DomEvent.preventDefault) + .addListener(link, 'click', fn, context); + + L.DomEvent + .addListener(container, fullScreenApi.fullScreenEventName, L.DomEvent.stopPropagation) + .addListener(container, fullScreenApi.fullScreenEventName, L.DomEvent.preventDefault) + .addListener(container, fullScreenApi.fullScreenEventName, this._handleEscKey, context); + + L.DomEvent + .addListener(document, fullScreenApi.fullScreenEventName, L.DomEvent.stopPropagation) + .addListener(document, fullScreenApi.fullScreenEventName, L.DomEvent.preventDefault) + .addListener(document, fullScreenApi.fullScreenEventName, this._handleEscKey, context); + + return link; + }, + + toogleFullScreen: function () { + this._exitFired = false; + var container = this._container; + if (this._isFullscreen) { + if (fullScreenApi.supportsFullScreen) { + fullScreenApi.cancelFullScreen(container); + } else { + L.DomUtil.removeClass(container, 'leaflet-pseudo-fullscreen'); + } + this.invalidateSize(); + this.fire('exitFullscreen'); + this._exitFired = true; + this._isFullscreen = false; + } else { + if (fullScreenApi.supportsFullScreen) { + fullScreenApi.requestFullScreen(container); + } else { + L.DomUtil.addClass(container, 'leaflet-pseudo-fullscreen'); + } + this.invalidateSize(); + this.fire('enterFullscreen'); + this._isFullscreen = true; + } + }, + + _handleEscKey: function () { + if (!fullScreenApi.isFullScreen(this) && !this._exitFired) { + this.fire('exitFullscreen'); + this._exitFired = true; + this._isFullscreen = false; + } + } + }); + + L.Map.addInitHook(function () { + if (this.options.fullscreenControl) { + this.fullscreenControl = L.control.fullscreen(this.options.fullscreenControlOptions); + this.addControl(this.fullscreenControl); + } + }); + + L.control.fullscreen = function (options) { + return new L.Control.FullScreen(options); + }; + + /* Native FullScreen JavaScript API ------------- Assumes Mozilla naming conventions instead of W3C for now - + source : http://johndyer.name/native-fullscreen-javascript-api-plus-jquery-plugin/ - + */ - var fullScreenApi = { - supportsFullScreen: false, - isFullScreen: function () { - return false; - }, - requestFullScreen: function () { }, - cancelFullScreen: function () { }, - fullScreenEventName: '', - prefix: '', - }, - browserPrefixes = 'webkit moz o ms khtml'.split(' '); - - // check for native support - if (typeof document.exitFullscreen !== 'undefined') { - fullScreenApi.supportsFullScreen = true; - } else { - // check for fullscreen support by vendor prefix - for (var i = 0, il = browserPrefixes.length; i < il; i++) { - fullScreenApi.prefix = browserPrefixes[i]; - if ( - typeof document[fullScreenApi.prefix + 'CancelFullScreen'] !== - 'undefined' - ) { - fullScreenApi.supportsFullScreen = true; - break; - } - } - } - - // update methods to do something useful - if (fullScreenApi.supportsFullScreen) { - fullScreenApi.fullScreenEventName = - fullScreenApi.prefix + 'fullscreenchange'; - fullScreenApi.isFullScreen = function () { - switch (this.prefix) { - case '': - return document.fullScreen; - case 'webkit': - return document.webkitIsFullScreen; - default: - return document[this.prefix + 'FullScreen']; - } - }; - fullScreenApi.requestFullScreen = function (el) { - return this.prefix === '' - ? el.requestFullscreen() - : el[this.prefix + 'RequestFullScreen'](); - }; - fullScreenApi.cancelFullScreen = function (el) { - return this.prefix === '' - ? document.exitFullscreen() - : document[this.prefix + 'CancelFullScreen'](); - }; - } - - // jQuery plugin - if (typeof jQuery !== 'undefined') { - jQuery.fn.requestFullScreen = function () { - return this.each(function () { - var el = jQuery(this); - if (fullScreenApi.supportsFullScreen) { - fullScreenApi.requestFullScreen(el); - } - }); - }; - } - - // export api - window.fullScreenApi = fullScreenApi; + var fullScreenApi = { + supportsFullScreen: false, + isFullScreen: function () { return false; }, + requestFullScreen: function () { }, + cancelFullScreen: function () { }, + fullScreenEventName: '', + prefix: '' + }, + browserPrefixes = 'webkit moz o ms khtml'.split(' '); + + // check for native support + if (typeof document.exitFullscreen !== 'undefined') { + fullScreenApi.supportsFullScreen = true; + } else { + // check for fullscreen support by vendor prefix + for (var i = 0, il = browserPrefixes.length; i < il; i++) { + fullScreenApi.prefix = browserPrefixes[i]; + if (typeof document[fullScreenApi.prefix + 'CancelFullScreen'] !== 'undefined') { + fullScreenApi.supportsFullScreen = true; + break; + } + } + } + + // update methods to do something useful + if (fullScreenApi.supportsFullScreen) { + fullScreenApi.fullScreenEventName = fullScreenApi.prefix + 'fullscreenchange'; + fullScreenApi.isFullScreen = function () { + switch (this.prefix) { + case '': + return document.fullScreen; + case 'webkit': + return document.webkitIsFullScreen; + default: + return document[this.prefix + 'FullScreen']; + } + }; + fullScreenApi.requestFullScreen = function (el) { + return (this.prefix === '') ? el.requestFullscreen() : el[this.prefix + 'RequestFullScreen'](); + }; + fullScreenApi.cancelFullScreen = function (el) { + return (this.prefix === '') ? document.exitFullscreen() : document[this.prefix + 'CancelFullScreen'](); + }; + } + + // jQuery plugin + if (typeof jQuery !== 'undefined') { + jQuery.fn.requestFullScreen = function () { + return this.each(function () { + var el = jQuery(this); + if (fullScreenApi.supportsFullScreen) { + fullScreenApi.requestFullScreen(el); + } + }); + }; + } + + // export api + window.fullScreenApi = fullScreenApi; })(); + /* # L.Control.IIP adjusts the rendering of an IIP layer # (see http://iipimage.sourceforge.net/documentation/protocol/) @@ -3948,119 +3351,102 @@ if (typeof require !== 'undefined') { */ if (typeof require !== 'undefined') { - var $ = require('jquery'); + var $ = require('jquery'); } L.Control.IIP = L.Control.extend({ - options: { - title: 'a control related to IIPImage', - collapsed: true, - position: 'topleft', - }, - - initialize: function (baseLayers, options) { - L.setOptions(this, options); - this._className = 'leaflet-control-iip'; - this._id = 'leaflet-iipimage'; - this._layers = baseLayers; - }, - - // addTo can be used to add the regular leaflet controls or to the sidebar - addTo: function (dest) { - if (dest._sidebar) { - this._sidebar = dest; - // dest is a sidebar class instance - this._map = dest._map; - this._dialog = L.DomUtil.create('div', this._className + '-dialog'); - dest.addTab( - this._id, - this._className, - this.options.title, - this._dialog, - this._sideClass - ); - this._map.on('layeradd', this._checkIIP, this); - return dest; - } else { - return L.Control.prototype.addTo.call(this, dest); - } - }, - - onAdd: function (map) { - var className = this._className, - id = this._id, - container = (this._container = L.DomUtil.create( - 'div', - className + ' leaflet-bar' - )); - //Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released - container.setAttribute('aria-haspopup', true); - - L.DomEvent.disableClickPropagation(container).disableScrollPropagation( - container - ); - - this._dialog = L.DomUtil.create('div', className + '-dialog', container); - if (this.options.collapsed) { - if (!L.Browser.android) { - L.DomEvent.on(container, 'mouseover', this._expand, this).on( - container, - 'mouseout', - this._collapse, - this - ); - } - - var toggle = (this._toggle = L.DomUtil.create( - 'a', - className + '-toggle leaflet-bar', - container - )); - toggle.href = '#'; - toggle.id = id + '-toggle'; - toggle.title = this.options.title; - - if (L.Browser.touch) { - L.DomEvent.on(toggle, 'click', L.DomEvent.stop).on( - toggle, - 'click', - this._expand, - this - ); - } else { - L.DomEvent.on(toggle, 'focus', this._expand, this); - } - - this._map.on('click', this._collapse, this); - // TODO keyboard accessibility - } else { - this._expand(); - } - - // this._checkIIP(); - this._map.on('layeradd', this._checkIIP, this); - - return this._container; - }, - - _checkIIP: function (e) { - var layer = e.layer; - - // Exit if not an IIP layer - if (!layer || !layer.iipdefault) { - return; - } - this._layer = layer; - if (this._reloadFlag) { - layer.once('load', this._resetDialog, this); - } else { - this._initDialog(); - this._reloadFlag = true; - } - }, - - _initDialog: function () { - /* + options: { + title: 'a control related to IIPImage', + collapsed: true, + position: 'topleft' + }, + + initialize: function (baseLayers, options) { + L.setOptions(this, options); + this._className = 'leaflet-control-iip'; + this._id = 'leaflet-iipimage'; + this._layers = baseLayers; + }, + + // addTo can be used to add the regular leaflet controls or to the sidebar + addTo: function (dest) { + if (dest._sidebar) { + this._sidebar = dest; + // dest is a sidebar class instance + this._map = dest._map; + this._dialog = L.DomUtil.create('div', this._className + '-dialog'); + dest.addTab(this._id, this._className, this.options.title, this._dialog, + this._sideClass); + this._map.on('layeradd', this._checkIIP, this); + return dest; + } else { + return L.Control.prototype.addTo.call(this, dest); + } + }, + + onAdd: function (map) { + var className = this._className, + id = this._id, + container = this._container = L.DomUtil.create('div', className + ' leaflet-bar'); + //Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released + container.setAttribute('aria-haspopup', true); + + L.DomEvent + .disableClickPropagation(container) + .disableScrollPropagation(container); + + this._dialog = L.DomUtil.create('div', className + '-dialog', container); + if (this.options.collapsed) { + if (!L.Browser.android) { + L.DomEvent + .on(container, 'mouseover', this._expand, this) + .on(container, 'mouseout', this._collapse, this); + } + + var toggle = this._toggle = L.DomUtil.create('a', className + '-toggle leaflet-bar', container); + toggle.href = '#'; + toggle.id = id + '-toggle'; + toggle.title = this.options.title; + + if (L.Browser.touch) { + L.DomEvent + .on(toggle, 'click', L.DomEvent.stop) + .on(toggle, 'click', this._expand, this); + } + else { + L.DomEvent.on(toggle, 'focus', this._expand, this); + } + + this._map.on('click', this._collapse, this); + // TODO keyboard accessibility + } else { + this._expand(); + } + + // this._checkIIP(); + this._map.on('layeradd', this._checkIIP, this); + + return this._container; + }, + + _checkIIP: function (e) { + var layer = e.layer; + + // Exit if not an IIP layer + if (!layer || !layer.iipdefault) { + return; + } + this._layer = layer; + if (this._reloadFlag) { + layer.once('load', this._resetDialog, this); + } else { + this._initDialog(); + this._reloadFlag = true; + } + }, + + _initDialog: function () { + /* var className = this._className, container = this._container, dialog = this._dialog, @@ -4068,446 +3454,383 @@ L.Control.IIP = L.Control.extend({ layer = this._layer; dialog.innerHTML = ''; */ - // Setup the rest of the dialog window here - }, - - _resetDialog: function () { - this._dialog.innerHTML = ''; - this._initDialog(); - }, - - _addDialogBox: function (id) { - var box = L.DomUtil.create('div', this._className + '-box', this._dialog); - if (id) { - box.id = id; - } - return box; - }, - - _addDialogLine: function (label, dialogBox) { - var line = L.DomUtil.create('div', this._className + '-line', dialogBox), - text = L.DomUtil.create('div', this._className + '-label', line); - text.innerHTML = label; - return line; - }, - - _addDialogElement: function (line) { - return L.DomUtil.create('div', this._className + '-element', line); - }, - - _expand: function () { - L.DomUtil.addClass(this._container, this._className + '-expanded'); - }, - - _collapse: function () { - this._container.className = this._container.className.replace( - ' ' + this._className + '-expanded', - '' - ); - }, - - /** - * Get currently active base layer on the map - * @return {Object} l where l.name - layer name on the control, - * l.layer is L.TileLayer, l.overlay is overlay layer. - */ - getActiveBaseLayer: function () { - return this._activeBaseLayer; - }, - - /** - * Get currently active overlay layers on the map - * @return {{layerId: l}} where layerId is L.stamp(l.layer) - * and l @see #getActiveBaseLayer jsdoc. - */ - - _findActiveBaseLayer: function () { - var layers = this._layers; - this._prelayer = undefined; - for (var layername in layers) { - var layer = layers[layername]; - if (!layer.overlay) { - if (!layer._map) { - this._prelayer = layer; - } else if (this._map.hasLayer(layer) && layer.iipdefault) { - return layer; - } - } - } - return undefined; - }, - - _createButton: function (className, parent, subClassName, fn, title) { - var button = L.DomUtil.create('a', className, parent); - button.target = '_blank'; - if (subClassName) { - button.id = className + '-' + subClassName; - } - if (fn) { - L.DomEvent.on(button, 'click touch', fn, this); - } - if (title) { - button.title = title; - } - return button; - }, - - _createRadioButton: function (className, parent, value, checked, fn, title) { - var button = L.DomUtil.create('input', className, parent); - - button.type = 'radio'; - button.name = className; - button.value = value; - button.checked = checked; - if (fn) { - L.DomEvent.on(button, 'click touch', fn, value); - } - - var label = L.DomUtil.create('label', className, parent); - - label.htmlFor = button.id = className + '-' + value; - if (title) { - label.title = title; - } - return button; - }, - - _createSelectMenu: function ( - className, - parent, - items, - disabled, - selected, - fn, - title - ) { - // Wrapper around the select element for better positioning and sizing - var div = L.DomUtil.create('div', className, parent), - select = L.DomUtil.create('select', className, div), - choose = document.createElement('option'), - opt = (select.opt = []), - index; - - choose.text = 'choose'; - choose.disabled = true; - if (!selected || selected < 0) { - choose.selected = true; - } - select.add(choose, null); - for (var i in items) { - index = parseInt(i, 10); - opt[index] = document.createElement('option'); - opt[index].text = items[index]; - opt[index].value = index; - if (disabled && disabled[index]) { - opt[index].disabled = true; - } else if (index === selected) { - opt[index].selected = true; - } - select.add(opt[index], null); - } - - // Fix collapsing dialog issue when selecting a channel - if (this._container && !L.Browser.android && this.options.collapsed) { - L.DomEvent.on( - select, - 'mousedown', - function () { - L.DomEvent.off(this._container, 'mouseout', this._collapse, this); - this.collapsedOff = true; - }, - this - ); - - L.DomEvent.on( - this._container, - 'mouseover', - function () { - if (this.collapsedOff) { - L.DomEvent.on(this._container, 'mouseout', this._collapse, this); - this.collapsedOff = false; - } - }, - this - ); - } - - if (fn) { - L.DomEvent.on(select, 'change keyup', fn, this); - } - if (title) { - div.title = title; - } - - return select; - }, - - _createColorPicker: function ( - className, - parent, - subClassName, - defaultColor, - fn, - storageKey, - title - ) { - var _this = this, - colpick = L.DomUtil.create('input', className, parent); - - colpick.type = 'color'; - colpick.value = defaultColor; - colpick.id = className + '-' + subClassName; - - $(document).ready(function () { - $(colpick) - .spectrum({ - showInput: true, - appendTo: '#' + _this._id, - showPaletteOnly: true, - togglePaletteOnly: true, - localStorageKey: storageKey, - change: function (color) { - colpick.value = color.toHexString(); - }, - }) - .on('show.spectrum', function () { - if (_this._container) { - L.DomEvent.off(_this._container, 'mouseout', _this._collapse); - } - }); - if (fn) { - $(colpick).on('change', fn); - } - if (title) { - $('#' + colpick.id + '+.sp-replacer').prop('title', title); - } - }); - - return colpick; - }, - - _addSwitchInput: function (layer, box, label, attr, title, id, checked) { - var line = this._addDialogLine(label, box), - elem = this._addDialogElement(line), - flip = (elem.flip = L.flipswitch(elem, { - checked: checked, - id: id, - title: title, - })); - - flip.on( - 'change', - function () { - this._onInputChange(layer, attr, flip.value()); - }, - this - ); - - return elem; - }, - - _addNumericalInput: function ( - layer, - box, - label, - attr, - title, - id, - initValue, - step, - min, - max, - func - ) { - var line = this._addDialogLine(label, box), - elem = this._addDialogElement(line), - spinbox = (elem.spinbox = L.spinbox(elem, { - step: step, - dmin: min, - dmax: max, - initValue: initValue, - title: title, - })); - - spinbox.on( - 'change', - function () { - L.IIPUtils.flashElement(spinbox._input); - this._onInputChange(layer, attr, spinbox.value(), func); - }, - this - ); - - return elem; - }, - - _updateInput: function (elem, value) { - if (elem.spinbox) { - elem.spinbox.value(value); - } else if (elem.flip) { - elem.flip.value(value); - } - }, - - _spinboxStep: function (min, max) { - var step = parseFloat( - (Math.abs(max === min ? max : max - min) * 0.01).toPrecision(1) - ); - - return step === 0.0 ? 1.0 : step; - }, - - _onInputChange: function (layer, pname, value, func) { - var pnamearr = pname.split(/\[|\]/); - if (pnamearr[1]) { - layer[pnamearr[0]][parseInt(pnamearr[1], 10)] = value; - } else { - layer[pnamearr[0]] = value; - } - if (func) { - func(layer); - } - layer.redraw(); - }, - - _updateLayerList: function () { - if (!this._dialog) { - return this; - } - - if (this._layerList) { - L.DomUtil.empty(this._layerList); - } else { - this._layerList = L.DomUtil.create( - 'div', - 'leaflet-control-iip' + '-layerlist', - this._dialog - ); - } - - for (var i in this._layers) { - this._addLayerItem(this._layers[i]); - } - - return this; - }, - - _addLayerItem: function (obj) { - var _this = this, - layerItem = L.DomUtil.create('div', 'leaflet-control-iip-layer'), - inputdiv = L.DomUtil.create( - 'div', - 'leaflet-control-iip-layerswitch', - layerItem - ); - - if (obj.layer.notReady) { - L.DomUtil.create('div', 'leaflet-control-iip-activity', inputdiv); - } else { - var input, - checked = this._map.hasLayer(obj.layer); - input = document.createElement('input'); - input.type = 'checkbox'; - input.className = 'leaflet-control-iip-selector'; - input.defaultChecked = checked; - input.layerId = L.stamp(obj.layer); - L.DomEvent.on( - input, - 'click', - function () { - var i, - input, - obj, - inputs = this._layerList.getElementsByTagName('input'), - inputsLen = inputs.length; - - this._handlingClick = true; - - for (i = 0; i < inputsLen; i++) { - input = inputs[i]; - if (!('layerId' in input)) { - continue; - } - obj = this._layers[input.layerId]; - if (input.checked && !this._map.hasLayer(obj.layer)) { - obj.layer.addTo(this._map); - } else if (!input.checked && this._map.hasLayer(obj.layer)) { - this._map.removeLayer(obj.layer); - } - } - - this._handlingClick = false; - }, - this - ); - inputdiv.appendChild(input); - } - - var name = L.DomUtil.create( - 'div', - 'leaflet-control-iip-layername', - layerItem - ); - name.innerHTML = ' ' + obj.name; - name.style.textShadow = '0px 0px 5px ' + obj.layer.nameColor; - - this._createButton( - 'leaflet-control-iip-trash', - layerItem, - undefined, - function () { - _this.removeLayer(obj.layer); - if (!obj.notReady) { - _this._map.removeLayer(obj.layer); - } - }, - 'Delete layer' - ); - - this._layerList.appendChild(layerItem); - - return layerItem; - }, - - addLayer: function (layer, name, index) { - layer.on('add remove', this._onLayerChange, this); - - var id = L.stamp(layer); - - this._layers[id] = { - layer: layer, - name: name, - index: index, - }; - - return this._updateLayerList(); - }, - - removeLayer: function (layer) { - layer.off('add remove', this._onLayerChange, this); - layer.fire('trash', { index: this._layers[L.stamp(layer)].index }); - layer.off('trash'); - - delete this._layers[L.stamp(layer)]; - return this._updateLayerList(); - }, - - _onLayerChange: function (e) { - if (!this._handlingClick) { - this._updateLayerList(); - } - - var obj = this._layers[L.stamp(e.target)], - type = e.type === 'add' ? 'overlayadd' : 'overlayremove'; - - this._map.fire(type, obj); - }, + // Setup the rest of the dialog window here + }, + + _resetDialog: function () { + this._dialog.innerHTML = ''; + this._initDialog(); + }, + + _addDialogBox: function (id) { + var box = L.DomUtil.create('div', this._className + '-box', this._dialog); + if (id) { + box.id = id; + } + return box; + }, + + _addDialogLine: function (label, dialogBox) { + var line = L.DomUtil.create('div', this._className + '-line', dialogBox), + text = L.DomUtil.create('div', this._className + '-label', line); + text.innerHTML = label; + return line; + }, + + _addDialogElement: function (line) { + return L.DomUtil.create('div', this._className + '-element', line); + }, + + _expand: function () { + L.DomUtil.addClass(this._container, this._className + '-expanded'); + }, + + _collapse: function () { + this._container.className = this._container.className.replace(' ' + this._className + '-expanded', ''); + }, + + /** + * Get currently active base layer on the map + * @return {Object} l where l.name - layer name on the control, + * l.layer is L.TileLayer, l.overlay is overlay layer. + */ + getActiveBaseLayer: function () { + return this._activeBaseLayer; + }, + + /** + * Get currently active overlay layers on the map + * @return {{layerId: l}} where layerId is L.stamp(l.layer) + * and l @see #getActiveBaseLayer jsdoc. + */ + + _findActiveBaseLayer: function () { + var layers = this._layers; + this._prelayer = undefined; + for (var layername in layers) { + var layer = layers[layername]; + if (!layer.overlay) { + if (!layer._map) { + this._prelayer = layer; + } else if (this._map.hasLayer(layer) && layer.iipdefault) { + return layer; + } + } + } + return undefined; + }, + + _createButton: function (className, parent, subClassName, fn, title) { + var button = L.DomUtil.create('a', className, parent); + button.target = '_blank'; + if (subClassName) { + button.id = className + '-' + subClassName; + } + if (fn) { + L.DomEvent.on(button, 'click touch', fn, this); + } + if (title) { + button.title = title; + } + return button; + }, + + _createRadioButton: function (className, parent, value, checked, fn, title) { + var button = L.DomUtil.create('input', className, parent); + + button.type = 'radio'; + button.name = className; + button.value = value; + button.checked = checked; + if (fn) { + L.DomEvent.on(button, 'click touch', fn, value); + } + + var label = L.DomUtil.create('label', className, parent); + + label.htmlFor = button.id = className + '-' + value; + if (title) { + label.title = title; + } + return button; + }, + + _createSelectMenu: function (className, parent, items, disabled, selected, fn, title) { + // Wrapper around the select element for better positioning and sizing + var div = L.DomUtil.create('div', className, parent), + select = L.DomUtil.create('select', className, div), + choose = document.createElement('option'), + opt = select.opt = [], + index; + + choose.text = 'choose'; + choose.disabled = true; + if (!selected || selected < 0) { + choose.selected = true; + } + select.add(choose, null); + for (var i in items) { + index = parseInt(i, 10); + opt[index] = document.createElement('option'); + opt[index].text = items[index]; + opt[index].value = index; + if (disabled && disabled[index]) { + opt[index].disabled = true; + } else if (index === selected) { + opt[index].selected = true; + } + select.add(opt[index], null); + } + + // Fix collapsing dialog issue when selecting a channel + if (this._container && !L.Browser.android && this.options.collapsed) { + L.DomEvent.on(select, 'mousedown', function () { + L.DomEvent.off(this._container, 'mouseout', this._collapse, this); + this.collapsedOff = true; + }, this); + + L.DomEvent.on(this._container, 'mouseover', function () { + if (this.collapsedOff) { + L.DomEvent.on(this._container, 'mouseout', this._collapse, this); + this.collapsedOff = false; + } + }, this); + } + + if (fn) { + L.DomEvent.on(select, 'change keyup', fn, this); + } + if (title) { + div.title = title; + } + + return select; + }, + + + _createColorPicker: function (className, parent, subClassName, defaultColor, + fn, storageKey, title) { + var _this = this, + colpick = L.DomUtil.create('input', className, parent); + + colpick.type = 'color'; + colpick.value = defaultColor; + colpick.id = className + '-' + subClassName; + + $(document).ready(function () { + $(colpick).spectrum({ + showInput: true, + appendTo: '#' + _this._id, + showPaletteOnly: true, + togglePaletteOnly: true, + localStorageKey: storageKey, + change: function (color) { + colpick.value = color.toHexString(); + } + }).on('show.spectrum', function () { + if (_this._container) { + L.DomEvent.off(_this._container, 'mouseout', _this._collapse); + } + }); + if (fn) { + $(colpick).on('change', fn); + } + if (title) { + $('#' + colpick.id + '+.sp-replacer').prop('title', title); + } + }); + + return colpick; + }, + + + _addSwitchInput: function (layer, box, label, attr, title, id, checked) { + var line = this._addDialogLine(label, box), + elem = this._addDialogElement(line), + flip = elem.flip = L.flipswitch(elem, { + checked: checked, + id: id, + title: title + }); + + flip.on('change', function () { + this._onInputChange(layer, attr, flip.value()); + }, this); + + return elem; + }, + + _addNumericalInput: function (layer, box, label, attr, title, id, initValue, + step, min, max, func) { + var line = this._addDialogLine(label, box), + elem = this._addDialogElement(line), + spinbox = elem.spinbox = L.spinbox(elem, { + step: step, + dmin: min, + dmax: max, + initValue: initValue, + title: title + }); + + spinbox.on('change', function () { + L.IIPUtils.flashElement(spinbox._input); + this._onInputChange(layer, attr, spinbox.value(), func); + }, this); + + return elem; + }, + + _updateInput: function (elem, value) { + if (elem.spinbox) { + elem.spinbox.value(value); + } else if (elem.flip) { + elem.flip.value(value); + } + }, + + _spinboxStep: function (min, max) { + var step = parseFloat((Math.abs(max === min ? max : max - min) * + 0.01).toPrecision(1)); + + return step === 0.0 ? 1.0 : step; + }, + + _onInputChange: function (layer, pname, value, func) { + + var pnamearr = pname.split(/\[|\]/); + if (pnamearr[1]) { + layer[pnamearr[0]][parseInt(pnamearr[1], 10)] = value; + } else { + layer[pnamearr[0]] = value; + } + if (func) { + func(layer); + } + layer.redraw(); + }, + + _updateLayerList: function () { + if (!this._dialog) { + return this; + } + + if (this._layerList) { + L.DomUtil.empty(this._layerList); + } else { + this._layerList = L.DomUtil.create('div', 'leaflet-control-iip' + '-layerlist', + this._dialog); + } + + for (var i in this._layers) { + this._addLayerItem(this._layers[i]); + } + + return this; + }, + + _addLayerItem: function (obj) { + var _this = this, + layerItem = L.DomUtil.create('div', 'leaflet-control-iip-layer'), + inputdiv = L.DomUtil.create('div', 'leaflet-control-iip-layerswitch', layerItem); + + if (obj.layer.notReady) { + L.DomUtil.create('div', 'leaflet-control-iip-activity', inputdiv); + } else { + var input, + checked = this._map.hasLayer(obj.layer); + input = document.createElement('input'); + input.type = 'checkbox'; + input.className = 'leaflet-control-iip-selector'; + input.defaultChecked = checked; + input.layerId = L.stamp(obj.layer); + L.DomEvent.on(input, 'click', function () { + var i, input, obj, + inputs = this._layerList.getElementsByTagName('input'), + inputsLen = inputs.length; + + this._handlingClick = true; + + for (i = 0; i < inputsLen; i++) { + input = inputs[i]; + if (!('layerId' in input)) { + continue; + } + obj = this._layers[input.layerId]; + if (input.checked && !this._map.hasLayer(obj.layer)) { + obj.layer.addTo(this._map); + } else if (!input.checked && this._map.hasLayer(obj.layer)) { + this._map.removeLayer(obj.layer); + } + } + + this._handlingClick = false; + }, this); + inputdiv.appendChild(input); + } + + var name = L.DomUtil.create('div', 'leaflet-control-iip-layername', layerItem); + name.innerHTML = ' ' + obj.name; + name.style.textShadow = '0px 0px 5px ' + obj.layer.nameColor; + + this._createButton('leaflet-control-iip-trash', + layerItem, + undefined, + function () { + _this.removeLayer(obj.layer); + if (!obj.notReady) { + _this._map.removeLayer(obj.layer); + } + }, + 'Delete layer' + ); + + this._layerList.appendChild(layerItem); + + return layerItem; + }, + + addLayer: function (layer, name, index) { + layer.on('add remove', this._onLayerChange, this); + + var id = L.stamp(layer); + + this._layers[id] = { + layer: layer, + name: name, + index: index + }; + + return this._updateLayerList(); + }, + + removeLayer: function (layer) { + layer.off('add remove', this._onLayerChange, this); + layer.fire('trash', { index: this._layers[L.stamp(layer)].index }); + layer.off('trash'); + + delete this._layers[L.stamp(layer)]; + return this._updateLayerList(); + }, + + _onLayerChange: function (e) { + if (!this._handlingClick) { + this._updateLayerList(); + } + + var obj = this._layers[L.stamp(e.target)], + type = e.type === 'add' ? 'overlayadd' : 'overlayremove'; + + this._map.fire(type, obj); + } + }); L.control.iip = function (baseLayers, options) { - return new L.Control.IIP(baseLayers, options); + return new L.Control.IIP(baseLayers, options); }; + + /* # L.Control.IIP.Catalog manages catalog overlays # @@ -4520,314 +3843,267 @@ L.control.iip = function (baseLayers, options) { */ if (typeof require !== 'undefined') { - var $ = require('jquery'); + var $ = require('jquery'); } L.Control.IIP.Catalog = L.Control.IIP.extend({ - defaultCatalogs: [ - L.Catalog.GAIA_DR1, - L.Catalog['2MASS'], - L.Catalog.SDSS, - L.Catalog.PPMXL, - L.Catalog.Abell, - ], - - options: { - title: 'Catalog overlays', - collapsed: true, - position: 'topleft', - nativeCelsys: true, - color: '#FFFF00', - timeOut: 30, // seconds - authenticate: false, // string define a method used to authenticate - }, - - initialize: function (catalogs, options) { - L.setOptions(this, options); - this._className = 'leaflet-control-iip'; - this._id = 'leaflet-iipcatalog'; - this._layers = {}; - this._handlingClick = false; - this._sideClass = 'catalog'; - this._catalogs = catalogs ? catalogs : this.defaultCatalogs; - }, - - _initDialog: function () { - var className = this._className, - catalogs = this._catalogs, - box = this._addDialogBox(), - // CDS catalog overlay - line = this._addDialogLine('', box), - elem = this._addDialogElement(line), - colpick = this._createColorPicker( - className + '-color', - elem, - 'catalog', - this.options.color, - false, - 'iipCatalog', - 'Click to set catalog color' - ); - - var catselect = this._createSelectMenu( - this._className + '-select', - elem, - catalogs.map(function (catalog) { - return catalog.name; - }), - undefined, - -1, - function () { - var className = catalogs[catselect.selectedIndex - 1].className; - if (className === undefined) { - className = ''; - } - L.DomUtil.setClass(catselect, this._className + '-select ' + className); - return; - }, - 'Select Catalog' - ); - - L.DomEvent.on( - catselect, - 'change keyup', - function () { - var catalog = catalogs[catselect.selectedIndex - 1]; - catselect.title = catalog.attribution + ' from ' + catalog.service; - }, - this - ); - - elem = this._addDialogElement(line); - - this._createButton( - className + '-button', - elem, - 'catalog', - function () { - var index = catselect.selectedIndex - 1; // Ignore dummy 'Choose catalog' entry - if (index >= 0) { - var catalog = catalogs[index]; - catalog.color = colpick.value; - catselect.selectedIndex = 0; - catselect.title = 'Select Catalog'; - L.DomUtil.setClass(catselect, this._className + '-select '); - this._getCatalog(catalog, this.options.timeOut); - } - }, - 'Query catalog' - ); - }, - - _resetDialog: function () { - // Do nothing: no need to reset with layer changes - }, - - _getCatalog: function (catalog, timeout) { - var _this = this, - map = this._map, - wcs = map.options.crs, - sysflag = wcs.forceNativeCelsys && !this.options.nativeCelsys, - center = sysflag ? wcs.celsysToEq(map.getCenter()) : map.getCenter(), - b = map.getPixelBounds(), - z = map.getZoom(), - templayer = new L.LayerGroup(null); - - // Add a temporary "dummy" layer to activate a spinner sign - templayer.notReady = true; - this.addLayer(templayer, catalog.name); - - if (catalog.authenticate) { - this.options.authenticate = catalog.authenticate; - } else { - this.options.authenticate = false; - } - - // Compute the search cone - var lngfac = Math.abs(Math.cos((center.lat * Math.PI) / 180.0)), - c = sysflag - ? [ - wcs.celsysToEq(map.unproject(b.min, z)), - wcs.celsysToEq(map.unproject(L.point(b.min.x, b.max.y), z)), - wcs.celsysToEq(map.unproject(b.max, z)), - wcs.celsysToEq(map.unproject(L.point(b.max.x, b.min.y), z)), - ] - : [ - map.unproject(b.min, z), - map.unproject(L.point(b.min.x, b.max.y), z), - map.unproject(b.max, z), - map.unproject(L.point(b.max.x, b.min.y), z), - ], - sys; - if (wcs.forceNativeCelsys && this.options.nativeCelsys) { - switch (wcs.celsyscode) { - case 'ecliptic': - sys = 'E2000.0'; - break; - case 'galactic': - sys = 'G'; - break; - case 'supergalactic': - sys = 'S'; - break; - default: - sys = 'J2000.0'; - break; - } - } else { - sys = 'J2000.0'; - } - - if (catalog.regionType === 'box') { - // CDS box search - var dlng = - (Math.max( - wcs._deltaLng(c[0], center), - wcs._deltaLng(c[1], center), - wcs._deltaLng(c[2], center), - wcs._deltaLng(c[3], center) - ) - - Math.min( - wcs._deltaLng(c[0], center), - wcs._deltaLng(c[1], center), - wcs._deltaLng(c[2], center), - wcs._deltaLng(c[3], center) - )) * - lngfac, - dlat = - Math.max(c[0].lat, c[1].lat, c[2].lat, c[3].lat) - - Math.min(c[0].lat, c[1].lat, c[2].lat, c[3].lat); - if (dlat < 0.0001) { - dlat = 0.0001; - } - if (dlng < 0.0001) { - dlng = 0.0001; - } - - L.IIPUtils.requestURL( - L.Util.template( - catalog.url, - L.extend({ - sys: sys, - lng: center.lng.toFixed(6), - lat: center.lat.toFixed(6), - dlng: dlng.toFixed(4), - dlat: dlat.toFixed(4), - nmax: catalog.nmax + 1, - maglim: catalog.maglim, - }) - ), - 'getting ' + catalog.service + ' data', - function (context, httpRequest) { - _this._loadCatalog(catalog, templayer, context, httpRequest); - }, - this, - timeout - ); - } else { - // Regular cone search - var dr = Math.max( - wcs.distance(c[0], center), - wcs.distance(c[0], center), - wcs.distance(c[0], center), - wcs.distance(c[0], center) - ); - L.IIPUtils.requestURL( - L.Util.template( - catalog.url, - L.extend({ - sys: sys, - lng: center.lng.toFixed(6), - lat: center.lat.toFixed(6), - dr: dr.toFixed(4), - drm: (dr * 60.0).toFixed(4), - nmax: catalog.nmax + 1, - }) - ), - 'querying ' + catalog.service + ' data', - function (context, httpRequest) { - _this._loadCatalog(catalog, templayer, context, httpRequest); - }, - this, - this.options.timeOut - ); - } - }, - - _loadCatalog: function (catalog, templayer, _this, httpRequest) { - if (httpRequest.readyState === 4) { - if (httpRequest.status === 200) { - var wcs = _this._map.options.crs, - response = httpRequest.responseText, - geo = catalog.toGeoJSON(response), - geocatalog = L.geoJson(geo, { - onEachFeature: function (feature, layer) { - if (feature.properties && feature.properties.items) { - layer.bindPopup(catalog.popup(feature)); - } - }, - coordsToLatLng: function (coords) { - if (wcs.forceNativeCelsys) { - var latLng = wcs.eqToCelsys(L.latLng(coords[1], coords[0])); - return new L.LatLng(latLng.lat, latLng.lng, coords[2]); - } else { - return new L.LatLng(coords[1], coords[0], coords[2]); - } - }, - filter: function (feature) { - return catalog.filter(feature); - }, - pointToLayer: function (feature, latlng) { - return catalog.draw(feature, latlng); - }, - style: function (feature) { - return { color: catalog.color, weight: 2 }; - }, - }), - excessflag; - geocatalog.nameColor = catalog.color; - geocatalog.addTo(_this._map); - this.removeLayer(templayer); - if (geo.features.length > catalog.nmax) { - geo.features.pop(); - excessflag = true; - } - this.addLayer( - geocatalog, - catalog.name + - ' (' + - geo.features.length.toString() + - (excessflag ? '+ entries)' : ' entries)') - ); - if (excessflag) { - alert( - 'Selected area is too large: ' + - catalog.name + - ' sample has been truncated to the brightest ' + - catalog.nmax + - ' sources.' - ); - } - } else { - if (httpRequest.status !== 0) { - alert( - 'Error ' + - httpRequest.status + - ' while querying ' + - catalog.service + - '.' - ); - } - this.removeLayer(templayer); - } - } - }, + + defaultCatalogs: [ + L.Catalog.GAIA_DR1, + L.Catalog['2MASS'], + L.Catalog.SDSS, + L.Catalog.PPMXL, + L.Catalog.Abell + ], + + options: { + title: 'Catalog overlays', + collapsed: true, + position: 'topleft', + nativeCelsys: true, + color: '#FFFF00', + timeOut: 30, // seconds + authenticate: false // string define a method used to authenticate + }, + + initialize: function (catalogs, options) { + L.setOptions(this, options); + this._className = 'leaflet-control-iip'; + this._id = 'leaflet-iipcatalog'; + this._layers = {}; + this._handlingClick = false; + this._sideClass = 'catalog'; + this._catalogs = catalogs ? catalogs : this.defaultCatalogs; + }, + + _initDialog: function () { + var className = this._className, + catalogs = this._catalogs, + box = this._addDialogBox(), + // CDS catalog overlay + line = this._addDialogLine('', box), + elem = this._addDialogElement(line), + colpick = this._createColorPicker( + className + '-color', + elem, + 'catalog', + this.options.color, + false, + 'iipCatalog', + 'Click to set catalog color' + ); + + var catselect = this._createSelectMenu( + this._className + '-select', + elem, + catalogs.map(function (catalog) { return catalog.name; }), + undefined, + -1, + function () { + var className = catalogs[catselect.selectedIndex - 1].className; + if (className === undefined) { + className = ''; + } + L.DomUtil.setClass(catselect, this._className + '-select ' + className); + return; + }, + 'Select Catalog' + ); + + L.DomEvent.on(catselect, 'change keyup', function () { + var catalog = catalogs[catselect.selectedIndex - 1]; + catselect.title = catalog.attribution + ' from ' + catalog.service; + }, this); + + elem = this._addDialogElement(line); + + this._createButton(className + '-button', elem, 'catalog', function () { + var index = catselect.selectedIndex - 1; // Ignore dummy 'Choose catalog' entry + if (index >= 0) { + var catalog = catalogs[index]; + catalog.color = colpick.value; + catselect.selectedIndex = 0; + catselect.title = 'Select Catalog'; + L.DomUtil.setClass(catselect, this._className + '-select '); + this._getCatalog(catalog, this.options.timeOut); + } + }, 'Query catalog'); + }, + + _resetDialog: function () { + // Do nothing: no need to reset with layer changes + }, + + _getCatalog: function (catalog, timeout) { + var _this = this, + map = this._map, + wcs = map.options.crs, + sysflag = wcs.forceNativeCelsys && !this.options.nativeCelsys, + center = sysflag ? wcs.celsysToEq(map.getCenter()) : map.getCenter(), + b = map.getPixelBounds(), + z = map.getZoom(), + templayer = new L.LayerGroup(null); + + // Add a temporary "dummy" layer to activate a spinner sign + templayer.notReady = true; + this.addLayer(templayer, catalog.name); + + if (catalog.authenticate) { + this.options.authenticate = catalog.authenticate; + } else { + this.options.authenticate = false; + } + + // Compute the search cone + var lngfac = Math.abs(Math.cos(center.lat * Math.PI / 180.0)), + c = sysflag ? + [wcs.celsysToEq(map.unproject(b.min, z)), + wcs.celsysToEq(map.unproject(L.point(b.min.x, b.max.y), z)), + wcs.celsysToEq(map.unproject(b.max, z)), + wcs.celsysToEq(map.unproject(L.point(b.max.x, b.min.y), z))] : + [map.unproject(b.min, z), + map.unproject(L.point(b.min.x, b.max.y), z), + map.unproject(b.max, z), + map.unproject(L.point(b.max.x, b.min.y), z)], + sys; + if (wcs.forceNativeCelsys && this.options.nativeCelsys) { + switch (wcs.celsyscode) { + case 'ecliptic': + sys = 'E2000.0'; + break; + case 'galactic': + sys = 'G'; + break; + case 'supergalactic': + sys = 'S'; + break; + default: + sys = 'J2000.0'; + break; + } + } else { + sys = 'J2000.0'; + } + + if (catalog.regionType === 'box') { + // CDS box search + var dlng = (Math.max(wcs._deltaLng(c[0], center), + wcs._deltaLng(c[1], center), + wcs._deltaLng(c[2], center), + wcs._deltaLng(c[3], center)) - + Math.min(wcs._deltaLng(c[0], center), + wcs._deltaLng(c[1], center), + wcs._deltaLng(c[2], center), + wcs._deltaLng(c[3], center))) * lngfac, + dlat = Math.max(c[0].lat, c[1].lat, c[2].lat, c[3].lat) - + Math.min(c[0].lat, c[1].lat, c[2].lat, c[3].lat); + if (dlat < 0.0001) { + dlat = 0.0001; + } + if (dlng < 0.0001) { + dlng = 0.0001; + } + + L.IIPUtils.requestURL( + L.Util.template(catalog.url, L.extend({ + sys: sys, + lng: center.lng.toFixed(6), + lat: center.lat.toFixed(6), + dlng: dlng.toFixed(4), + dlat: dlat.toFixed(4), + nmax: catalog.nmax + 1, + maglim: catalog.maglim + })), + 'getting ' + catalog.service + ' data', + function (context, httpRequest) { + _this._loadCatalog(catalog, templayer, context, httpRequest); + }, + this, + timeout + ); + } else { + // Regular cone search + var dr = Math.max(wcs.distance(c[0], center), + wcs.distance(c[0], center), + wcs.distance(c[0], center), + wcs.distance(c[0], center)); + L.IIPUtils.requestURL( + L.Util.template(catalog.url, L.extend({ + sys: sys, + lng: center.lng.toFixed(6), + lat: center.lat.toFixed(6), + dr: dr.toFixed(4), + drm: (dr * 60.0).toFixed(4), + nmax: catalog.nmax + 1 + })), 'querying ' + catalog.service + ' data', function (context, httpRequest) { + _this._loadCatalog(catalog, templayer, context, httpRequest); + }, this, this.options.timeOut); + } + }, + + _loadCatalog: function (catalog, templayer, _this, httpRequest) { + if (httpRequest.readyState === 4) { + if (httpRequest.status === 200) { + var wcs = _this._map.options.crs, + response = httpRequest.responseText, + geo = catalog.toGeoJSON(response), + geocatalog = L.geoJson(geo, { + onEachFeature: function (feature, layer) { + if (feature.properties && feature.properties.items) { + layer.bindPopup(catalog.popup(feature)); + } + }, + coordsToLatLng: function (coords) { + if (wcs.forceNativeCelsys) { + var latLng = wcs.eqToCelsys(L.latLng(coords[1], coords[0])); + return new L.LatLng(latLng.lat, latLng.lng, coords[2]); + } else { + return new L.LatLng(coords[1], coords[0], coords[2]); + } + }, + filter: function (feature) { + return catalog.filter(feature); + }, + pointToLayer: function (feature, latlng) { + return catalog.draw(feature, latlng); + }, + style: function (feature) { + return { color: catalog.color, weight: 2 }; + } + }), + excessflag; + geocatalog.nameColor = catalog.color; + geocatalog.addTo(_this._map); + this.removeLayer(templayer); + if (geo.features.length > catalog.nmax) { + geo.features.pop(); + excessflag = true; + } + this.addLayer(geocatalog, catalog.name + + ' (' + geo.features.length.toString() + + (excessflag ? '+ entries)' : ' entries)')); + if (excessflag) { + alert('Selected area is too large: ' + catalog.name + + ' sample has been truncated to the brightest ' + catalog.nmax + ' sources.'); + } + } else { + if (httpRequest.status !== 0) { + alert('Error ' + httpRequest.status + ' while querying ' + + catalog.service + '.'); + } + this.removeLayer(templayer); + } + } + } + }); L.control.iip.catalog = function (catalogs, options) { - return new L.Control.IIP.Catalog(catalogs, options); + return new L.Control.IIP.Catalog(catalogs, options); }; + + /* # L.Control.IIP.Channel manages the channel mixing of an IIP layer # (see http://iipimage.sourceforge.net/documentation/protocol/) @@ -4840,486 +4116,386 @@ L.control.iip.catalog = function (catalogs, options) { */ if (typeof require !== 'undefined') { - var $ = require('jquery'); + var $ = require('jquery'); } L.Control.IIP.Channel = L.Control.IIP.extend({ - options: { - title: 'Channel mixing', - collapsed: true, - cMap: 'grey', - mixingMode: null, // 'color' or 'mono' (or null for layer settings) - position: 'topleft', - }, - - initialize: function (mode, options) { - L.setOptions(this, options); - - this._className = 'leaflet-control-iip'; - this._id = 'leaflet-iipchannel'; - this._sideClass = 'channel'; - this._settings = []; - this._initsettings = []; - }, - - // Copy channel mixing settings from layer - saveSettings: function (layer, settings, mode) { - if (!settings[mode]) { - settings[mode] = {}; - } - - var setting = settings[mode], - nchan = layer.iipNChannel; - - setting.channel = layer.iipChannel; - setting.cMap = layer.iipCMap; - setting.rgb = []; - for (var c = 0; c < nchan; c++) { - setting.rgb[c] = layer.iipRGB[c].clone(); - } - }, - - // Copy channel mixing settings to layer - loadSettings: function (layer, settings, mode, keepchanflag) { - var setting = settings[mode], - nchan = layer.iipNChannel; - - if (!setting) { - return; - } - - if (!keepchanflag) { - layer.iipChannel = setting.channel; - } - layer.iipCMap = setting.cMap; - for (var c = 0; c < nchan; c++) { - layer.iipRGB[c] = setting.rgb[c].clone(); - } - }, - - _initDialog: function () { - var _this = this, - layer = this._layer, - className = this._className, - dialog = this._dialog; - - // copy initial IIP mixing parameters from the layer object - this.saveSettings(layer, this._initsettings, 'mono'); - this.saveSettings(layer, this._initsettings, 'color'); - - // copy current IIP mixing parameters from the layer object - this.saveSettings(layer, this._settings, 'mono'); - this.saveSettings(layer, this._settings, 'color'); - - this._mode = this.options.mixingMode - ? this.options.mixingMode - : layer.iipMode; - - var box = this._addDialogBox(), - modeline = this._addDialogLine('Mode:', box), - modelem = this._addDialogElement(modeline), - modeinput = L.DomUtil.create('div', className + '-radios', modelem), - elem, - modebutton; - - // Create Mode selection control section - modebutton = this._createRadioButton( - className + '-radio', - modeinput, - 'mono', - this._mode === 'mono', - function () { - // Save previous settings - _this.saveSettings(layer, _this._settings, _this._mode); - - // Remove previous dialogs - for (elem = box.lastChild; elem !== modeline; elem = box.lastChild) { - box.removeChild(elem); - } - for (elem = dialog.lastChild; elem !== box; elem = dialog.lastChild) { - dialog.removeChild(elem); - } - _this._channelList = undefined; - _this.loadSettings(layer, _this._settings, 'mono'); - _this._initMonoDialog(layer, box); - _this._mode = 'mono'; - }, - 'Select mono-channel palettized mode' - ); - - modebutton = this._createRadioButton( - className + '-radio', - modeinput, - 'color', - this._mode !== 'mono', - function () { - // Save previous settings - _this.saveSettings(layer, _this._settings, _this._mode); - // Remove previous dialogs - for (elem = box.lastChild; elem !== modeline; elem = box.lastChild) { - box.removeChild(elem); - } - for (elem = dialog.lastChild; elem !== box; elem = dialog.lastChild) { - dialog.removeChild(elem); - } - _this.loadSettings(layer, _this._settings, 'color'); - _this._channelList = undefined; - _this._initColorDialog(layer, box); - _this._mode = 'color'; - }, - 'Select color mixing mode' - ); - - if (_this._mode === 'mono') { - _this._initMonoDialog(layer, box); - } else { - _this._initColorDialog(layer, box); - } - }, - - _initMonoDialog: function (layer, box) { - // Single Channels with colour map - var _this = this, - channels = layer.iipChannelLabels, - className = this._className, - line = this._addDialogLine('Channel:', box), - elem = this._addDialogElement(line); - - layer.updateMono(); - - this._chanSelect = this._createSelectMenu( - this._className + '-select', - elem, - layer.iipChannelLabels, - undefined, - layer.iipChannel, - function () { - layer.iipChannel = parseInt(this._chanSelect.selectedIndex - 1, 10); - this._updateChannel(layer, layer.iipChannel); - layer.redraw(); - }, - 'Select image channel' - ); - - line = this._addDialogLine('LUT:', box); - elem = this._addDialogElement(line); - - var cmapinput = L.DomUtil.create('div', className + '-cmaps', elem), - cbutton = [], - cmaps = ['grey', 'jet', 'cold', 'hot'], - _changeMap = function () { - _this._onInputChange(layer, 'iipCMap', this); - }, - i; - for (i in cmaps) { - cbutton[i] = this._createRadioButton( - 'leaflet-cmap', - cmapinput, - cmaps[i], - cmaps[i] === this.options.cMap, - _changeMap, - '"' + - cmaps[i].charAt(0).toUpperCase() + - cmaps[i].substr(1) + - '" color-map' - ); - } - - this._addMinMax(layer, layer.iipChannel, box); - layer.redraw(); - }, - - _initColorDialog: function (layer, box) { - // Multiple Channels with mixing matrix - - var _this = this, - className = this._className, - line = this._addDialogLine('Channel:', box), - elem = this._addDialogElement(line), - colpick = (this._chanColPick = this._createColorPicker( - className + '-color', - elem, - 'channel', - layer.iipRGB[layer.iipChannel].toStr(), - function () { - var chan = layer.iipChannel, - hex = $(colpick).val(); - _this._updateMix(layer, chan, L.rgb(hex)); - _this.collapsedOff = true; - }, - 'iipChannel', - 'Click to set channel color' - )); - - this._onInputChange(layer, 'iipCMap', 'grey'); - layer.updateMix(); - - this._chanSelect = this._createSelectMenu( - this._className + '-select', - elem, - layer.iipChannelLabels, - undefined, - layer.iipChannel, - function () { - layer.iipChannel = this._chanSelect.selectedIndex - 1; - this._updateChannel(layer, layer.iipChannel, colpick); - }, - 'Select image channel' - ); - - this._addMinMax(layer, layer.iipChannel, box); - - line = this._addDialogLine('Colors:', box); - elem = this._addDialogElement(line); - - // Create reset color settings button - this._createButton( - className + '-button', - elem, - 'colormix-reset', - function () { - _this.loadSettings(layer, _this._initsettings, 'color', true); - layer.updateMix(); - this._updateColPick(layer); - this._updateChannelList(layer); - layer.redraw(); - }, - 'Reset color mix' - ); - - // Create automated color settings button - this._createButton( - className + '-button', - elem, - 'colormix-auto', - function () { - var nchan = layer.iipNChannel, - cc = 0, - nchanon = 0, - rgb = layer.iipRGB, - defcol = layer.iipdefault.channelColors; - - for (var c = 0; c < nchan; c++) { - if (rgb[c].isOn()) { - nchanon++; - } - } - if (nchanon >= defcol.length) { - nchanon = defcol.length - 1; - } - - for (c = 0; c < nchan; c++) { - if (rgb[c].isOn() && cc < nchanon) { - rgb[c] = L.rgb(defcol[nchanon][cc++]); - } - } - layer.updateMix(); - this._updateColPick(layer); - this._updateChannelList(layer); - layer.redraw(); - }, - 'Re-color active channels' - ); - - _this._updateChannelList(layer); - layer.redraw(); - }, - - // Add Spinboxes for setting the min and max clipping limits of pixel values - _addMinMax: function (layer, chan, box) { - var step = this._spinboxStep( - layer.iipMinValue[chan], - layer.iipMaxValue[chan] - ); - - // Min - this._minElem = this._addNumericalInput( - layer, - box, - 'Min:', - 'iipMinValue[' + chan + ']', - 'Lower clipping limit in ' + layer.iipChannelUnits[chan] + '.', - 'leaflet-channel-minvalue', - layer.iipMinValue[chan], - step - ); - - // Max - this._maxElem = this._addNumericalInput( - layer, - box, - 'Max:', - 'iipMaxValue[' + chan + ']', - 'Upper clipping limit in ' + layer.iipChannelUnits[chan] + '.', - 'leaflet-channel-maxvalue', - layer.iipMaxValue[chan], - step - ); - }, - - _updateChannel: function (layer, chan, colorElem) { - var _this = this, - step = this._spinboxStep( - layer.iipMinValue[chan], - layer.iipMaxValue[chan] - ); - _this._chanSelect.selectedIndex = chan + 1; - if (colorElem) { - $(colorElem).spectrum('set', layer.iipRGB[chan].toStr()); - $(colorElem) - .val(layer.iipRGB[chan].toStr()) - .off('change') - .on('change', function () { - _this._updateMix(layer, chan, L.rgb($(colorElem).val())); - }); - } - - this._minElem.spinbox - .value(layer.iipMinValue[chan]) - .step(step) - .off('change') - .on( - 'change', - function () { - _this._onInputChange( - layer, - 'iipMinValue[' + chan + ']', - _this._minElem.spinbox.value() - ); - }, - this - ); - - this._maxElem.spinbox - .value(layer.iipMaxValue[chan]) - .step(step) - .off('change') - .on( - 'change', - function () { - _this._onInputChange( - layer, - 'iipMaxValue[' + chan + ']', - _this._maxElem.spinbox.value() - ); - }, - this - ); - }, - - _updateMix: function (layer, chan, rgb) { - layer.rgbToMix(chan, rgb); - this._updateChannelList(layer); - layer.redraw(); - }, - - _updateChannelList: function (layer) { - var chanLabels = layer.iipChannelLabels, - chanList = this._channelList, - chanElems = this._channelElems, - trashElems = this._trashElems, - chanElem, - trashElem, - rgb, - color, - label, - c, - chan; - if (chanList) { - /* + options: { + title: 'Channel mixing', + collapsed: true, + cMap: 'grey', + mixingMode: null, // 'color' or 'mono' (or null for layer settings) + position: 'topleft', + }, + + initialize: function (mode, options) { + L.setOptions(this, options); + + this._className = 'leaflet-control-iip'; + this._id = 'leaflet-iipchannel'; + this._sideClass = 'channel'; + this._settings = []; + this._initsettings = []; + }, + + // Copy channel mixing settings from layer + saveSettings: function (layer, settings, mode) { + if (!settings[mode]) { + settings[mode] = {}; + } + + var setting = settings[mode], + nchan = layer.iipNChannel; + + setting.channel = layer.iipChannel; + setting.cMap = layer.iipCMap; + setting.rgb = []; + for (var c = 0; c < nchan; c++) { + setting.rgb[c] = layer.iipRGB[c].clone(); + } + }, + + // Copy channel mixing settings to layer + loadSettings: function (layer, settings, mode, keepchanflag) { + var setting = settings[mode], + nchan = layer.iipNChannel; + + if (!setting) { + return; + } + + if (!keepchanflag) { + layer.iipChannel = setting.channel; + } + layer.iipCMap = setting.cMap; + for (var c = 0; c < nchan; c++) { + layer.iipRGB[c] = setting.rgb[c].clone(); + } + }, + + _initDialog: function () { + var _this = this, + layer = this._layer, + className = this._className, + dialog = this._dialog; + + // copy initial IIP mixing parameters from the layer object + this.saveSettings(layer, this._initsettings, 'mono'); + this.saveSettings(layer, this._initsettings, 'color'); + + // copy current IIP mixing parameters from the layer object + this.saveSettings(layer, this._settings, 'mono'); + this.saveSettings(layer, this._settings, 'color'); + + this._mode = this.options.mixingMode ? + this.options.mixingMode : layer.iipMode; + + var box = this._addDialogBox(), + modeline = this._addDialogLine('Mode:', box), + modelem = this._addDialogElement(modeline), + modeinput = L.DomUtil.create('div', className + '-radios', modelem), + elem, modebutton; + + // Create Mode selection control section + modebutton = this._createRadioButton(className + '-radio', modeinput, 'mono', + (this._mode === 'mono'), function () { + // Save previous settings + _this.saveSettings(layer, _this._settings, _this._mode); + + // Remove previous dialogs + for (elem = box.lastChild; elem !== modeline; elem = box.lastChild) { + box.removeChild(elem); + } + for (elem = dialog.lastChild; elem !== box; elem = dialog.lastChild) { + dialog.removeChild(elem); + } + _this._channelList = undefined; + _this.loadSettings(layer, _this._settings, 'mono'); + _this._initMonoDialog(layer, box); + _this._mode = 'mono'; + }, 'Select mono-channel palettized mode'); + + modebutton = this._createRadioButton(className + '-radio', modeinput, 'color', + (this._mode !== 'mono'), function () { + // Save previous settings + _this.saveSettings(layer, _this._settings, _this._mode); + // Remove previous dialogs + for (elem = box.lastChild; elem !== modeline; elem = box.lastChild) { + box.removeChild(elem); + } + for (elem = dialog.lastChild; elem !== box; elem = dialog.lastChild) { + dialog.removeChild(elem); + } + _this.loadSettings(layer, _this._settings, 'color'); + _this._channelList = undefined; + _this._initColorDialog(layer, box); + _this._mode = 'color'; + }, 'Select color mixing mode'); + + if (_this._mode === 'mono') { + _this._initMonoDialog(layer, box); + } else { + _this._initColorDialog(layer, box); + } + }, + + _initMonoDialog: function (layer, box) { + // Single Channels with colour map + var _this = this, + channels = layer.iipChannelLabels, + className = this._className, + line = this._addDialogLine('Channel:', box), + elem = this._addDialogElement(line); + + layer.updateMono(); + + this._chanSelect = this._createSelectMenu( + this._className + '-select', + elem, + layer.iipChannelLabels, + undefined, + layer.iipChannel, + function () { + layer.iipChannel = parseInt(this._chanSelect.selectedIndex - 1, 10); + this._updateChannel(layer, layer.iipChannel); + layer.redraw(); + }, + 'Select image channel' + ); + + line = this._addDialogLine('LUT:', box); + elem = this._addDialogElement(line); + + var cmapinput = L.DomUtil.create('div', className + '-cmaps', elem), + cbutton = [], + cmaps = ['grey', 'jet', 'cold', 'hot'], + _changeMap = function () { + _this._onInputChange(layer, 'iipCMap', this); + }, + i; + for (i in cmaps) { + cbutton[i] = this._createRadioButton('leaflet-cmap', cmapinput, cmaps[i], + (cmaps[i] === this.options.cMap), _changeMap, + '"' + cmaps[i].charAt(0).toUpperCase() + cmaps[i].substr(1) + '" color-map'); + } + + this._addMinMax(layer, layer.iipChannel, box); + layer.redraw(); + }, + + _initColorDialog: function (layer, box) { + // Multiple Channels with mixing matrix + + var _this = this, + className = this._className, + line = this._addDialogLine('Channel:', box), + elem = this._addDialogElement(line), + colpick = this._chanColPick = this._createColorPicker( + className + '-color', + elem, + 'channel', + layer.iipRGB[layer.iipChannel].toStr(), + function () { + var chan = layer.iipChannel, + hex = $(colpick).val(); + _this._updateMix(layer, chan, L.rgb(hex)); + _this.collapsedOff = true; + }, + 'iipChannel', + 'Click to set channel color' + ); + + this._onInputChange(layer, 'iipCMap', 'grey'); + layer.updateMix(); + + this._chanSelect = this._createSelectMenu( + this._className + '-select', + elem, + layer.iipChannelLabels, + undefined, + layer.iipChannel, + function () { + layer.iipChannel = this._chanSelect.selectedIndex - 1; + this._updateChannel(layer, layer.iipChannel, colpick); + }, + 'Select image channel' + ); + + this._addMinMax(layer, layer.iipChannel, box); + + line = this._addDialogLine('Colors:', box); + elem = this._addDialogElement(line); + + // Create reset color settings button + this._createButton(className + '-button', elem, 'colormix-reset', function () { + _this.loadSettings(layer, _this._initsettings, 'color', true); + layer.updateMix(); + this._updateColPick(layer); + this._updateChannelList(layer); + layer.redraw(); + }, 'Reset color mix'); + + // Create automated color settings button + this._createButton(className + '-button', elem, 'colormix-auto', function () { + var nchan = layer.iipNChannel, + cc = 0, + nchanon = 0, + rgb = layer.iipRGB, + defcol = layer.iipdefault.channelColors; + + for (var c = 0; c < nchan; c++) { + if (rgb[c].isOn()) { + nchanon++; + } + } + if (nchanon >= defcol.length) { + nchanon = defcol.length - 1; + } + + for (c = 0; c < nchan; c++) { + if (rgb[c].isOn() && cc < nchanon) { + rgb[c] = L.rgb(defcol[nchanon][cc++]); + } + } + layer.updateMix(); + this._updateColPick(layer); + this._updateChannelList(layer); + layer.redraw(); + + }, 'Re-color active channels'); + + + _this._updateChannelList(layer); + layer.redraw(); + }, + + // Add Spinboxes for setting the min and max clipping limits of pixel values + _addMinMax: function (layer, chan, box) { + var step = this._spinboxStep(layer.iipMinValue[chan], layer.iipMaxValue[chan]); + + // Min + this._minElem = this._addNumericalInput(layer, box, 'Min:', + 'iipMinValue[' + chan + ']', + 'Lower clipping limit in ' + layer.iipChannelUnits[chan] + '.', + 'leaflet-channel-minvalue', layer.iipMinValue[chan], step); + + // Max + this._maxElem = this._addNumericalInput(layer, box, 'Max:', + 'iipMaxValue[' + chan + ']', + 'Upper clipping limit in ' + layer.iipChannelUnits[chan] + '.', + 'leaflet-channel-maxvalue', layer.iipMaxValue[chan], step); + }, + + _updateChannel: function (layer, chan, colorElem) { + var _this = this, + step = this._spinboxStep(layer.iipMinValue[chan], layer.iipMaxValue[chan]); + _this._chanSelect.selectedIndex = chan + 1; + if (colorElem) { + $(colorElem).spectrum('set', layer.iipRGB[chan].toStr()); + $(colorElem) + .val(layer.iipRGB[chan].toStr()) + .off('change') + .on('change', function () { + _this._updateMix(layer, chan, L.rgb($(colorElem).val())); + }); + } + + this._minElem.spinbox + .value(layer.iipMinValue[chan]) + .step(step) + .off('change') + .on('change', function () { + _this._onInputChange(layer, 'iipMinValue[' + chan + ']', + _this._minElem.spinbox.value()); + }, this); + + this._maxElem.spinbox + .value(layer.iipMaxValue[chan]) + .step(step) + .off('change') + .on('change', function () { + _this._onInputChange(layer, 'iipMaxValue[' + chan + ']', + _this._maxElem.spinbox.value()); + }, this); + }, + + _updateMix: function (layer, chan, rgb) { + layer.rgbToMix(chan, rgb); + this._updateChannelList(layer); + layer.redraw(); + }, + + _updateChannelList: function (layer) { + var chanLabels = layer.iipChannelLabels, + chanList = this._channelList, + chanElems = this._channelElems, + trashElems = this._trashElems, + chanElem, trashElem, rgb, color, label, c, chan; + if (chanList) { + /* for (c in chanElems) { L.DomEvent.off(chanElems[c], 'click touch'); L.DomEvent.off(trashElems[c], 'click touch'); } */ - L.DomUtil.empty(this._channelList); - } else { - chanList = this._channelList = L.DomUtil.create( - 'div', - this._className + '-chanlist', - this._dialog - ); - } - - chanElems = this._channelElems = []; - trashElems = this._trashElems = []; - - for (c in chanLabels) { - chan = parseInt(c, 10); - rgb = layer.iipRGB[chan]; - if (rgb.isOn()) { - chanElem = L.DomUtil.create( - 'div', - this._className + '-channel', - chanList - ); - color = L.DomUtil.create( - 'div', - this._className + '-chancolor', - chanElem - ); - color.style.backgroundColor = rgb.toStr(); - this._activateChanElem(color, layer, chan); - label = L.DomUtil.create( - 'div', - this._className + '-chanlabel', - chanElem - ); - label.innerHTML = chanLabels[c]; - this._activateChanElem(label, layer, chan); - trashElem = this._createButton( - 'leaflet-control-iip-trash', - chanElem, - undefined, - undefined, - 'Delete channel' - ); - this._activateTrashElem(trashElem, layer, chan); - chanElems.push(chanElem); - trashElems.push(trashElem); - } - } - }, - - _updateColPick: function (layer) { - $(this._chanColPick).spectrum( - 'set', - layer.iipRGB[layer.iipChannel].toStr() - ); - $(this._chanColPick).val(layer.iipRGB[layer.iipChannel].toStr()); - }, - - _activateTrashElem: function (trashElem, layer, chan) { - L.DomEvent.on( - trashElem, - 'click touch', - function () { - this._updateMix(layer, chan, L.rgb(0.0, 0.0, 0.0)); - if (layer === this._layer && chan === layer.iipChannel) { - this._updateColPick(layer); - } - }, - this - ); - }, - - _activateChanElem: function (chanElem, layer, chan) { - L.DomEvent.on( - chanElem, - 'click touch', - function () { - layer.iipChannel = chan; - this._updateChannel(layer, chan, this._chanColPick); - }, - this - ); - }, + L.DomUtil.empty(this._channelList); + } else { + chanList = this._channelList = L.DomUtil.create('div', this._className + '-chanlist', + this._dialog); + } + + chanElems = this._channelElems = []; + trashElems = this._trashElems = []; + + for (c in chanLabels) { + chan = parseInt(c, 10); + rgb = layer.iipRGB[chan]; + if (rgb.isOn()) { + chanElem = L.DomUtil.create('div', this._className + '-channel', chanList); + color = L.DomUtil.create('div', this._className + '-chancolor', chanElem); + color.style.backgroundColor = rgb.toStr(); + this._activateChanElem(color, layer, chan); + label = L.DomUtil.create('div', this._className + '-chanlabel', chanElem); + label.innerHTML = chanLabels[c]; + this._activateChanElem(label, layer, chan); + trashElem = this._createButton('leaflet-control-iip-trash', chanElem, + undefined, undefined, 'Delete channel'); + this._activateTrashElem(trashElem, layer, chan); + chanElems.push(chanElem); + trashElems.push(trashElem); + } + } + }, + + _updateColPick: function (layer) { + $(this._chanColPick).spectrum('set', layer.iipRGB[layer.iipChannel].toStr()); + $(this._chanColPick).val(layer.iipRGB[layer.iipChannel].toStr()); + }, + + _activateTrashElem: function (trashElem, layer, chan) { + L.DomEvent.on(trashElem, 'click touch', function () { + this._updateMix(layer, chan, L.rgb(0.0, 0.0, 0.0)); + if (layer === this._layer && chan === layer.iipChannel) { + this._updateColPick(layer); + } + }, this); + }, + + _activateChanElem: function (chanElem, layer, chan) { + L.DomEvent.on(chanElem, 'click touch', function () { + layer.iipChannel = chan; + this._updateChannel(layer, chan, this._chanColPick); + }, this); + } + }); L.control.iip.channel = function (options) { - return new L.Control.IIP.Channel(options); + return new L.Control.IIP.Channel(options); }; + + /* # L.Control.IIP.Doc adds online documentation to the VisiOmatic interface # (see http://iipimage.sourceforge.net/documentation/protocol/) @@ -5333,160 +4509,134 @@ L.control.iip.channel = function (options) { */ if (typeof require !== 'undefined') { - var $ = require('jquery'); + var $ = require('jquery'); } L.Control.IIP.Doc = L.Control.IIP.extend({ - options: { - title: 'Documentation', - collapsed: true, - position: 'topleft', - pdflink: undefined, - }, - - initialize: function (url, options) { - L.setOptions(this, options); - - this._className = 'leaflet-control-iip'; - this._id = 'leaflet-iipdoc'; - this._sideClass = 'doc'; - this._url = url; - }, - - _initDialog: function () { - var _this = this, - className = this._className, - layer = this._layer, - frameBox = L.DomUtil.create( - 'div', - this._className + '-framebox', - this._dialog - ), - iframe = (this._iframe = L.DomUtil.create( - 'iframe', - this._className + '-doc', - frameBox - )); - iframe.src = this._url; - iframe.frameborder = 0; - - this._navHistory = []; - this._navPos = 0; - this._ignore = false; - - L.DomEvent.on(iframe, 'load hashchange', this._onloadNav, this); - - var box = this._addDialogBox('leaflet-iipdoc-dialog'), - line = this._addDialogLine('Navigate:', box), - elem = this._addDialogElement(line); - - this._homeButton = this._createButton( - className + '-button', - elem, - 'home', - this._homeNav, - 'Navigate home' - ); - this._backButton = this._createButton( - className + '-button', - elem, - 'back', - this._backNav, - 'Navigate backward' - ); - this._forwardButton = this._createButton( - className + '-button', - elem, - 'forward', - this._forwardNav, - 'Navigate forward' - ); - - if (this.options.pdflink) { - var pdfButton = this._createButton( - className + '-button', - elem, - 'pdf', - undefined, - 'Download PDF version' - ); - pdfButton.href = this.options.pdflink; - } - }, - - // Update navigation buttons, based on http://stackoverflow.com/a/7704305 - _updateNav: function (newPos) { - if (newPos !== this._navPos) { - this._navPos = newPos; - this._navIgnore = true; - this._iframe.src = this._navHistory[this._navPos - 1]; - this._disableNav(); - } - }, - - _disableNav: function () { - // Enable / disable back button? - this._backButton.disabled = this._navPos === 1; - // Enable / disable forward button? - this._forwardButton.disabled = this._navPos >= this._navHistory.length; - }, - - // Navigate back in IFrame, based on http://stackoverflow.com/a/7704305 - _backNav: function () { - if (!this._backButton.disabled) { - this._updateNav(Math.max(1, this._navPos - 1)); - } - }, - - // Navigate forward in IFrame, based on http://stackoverflow.com/a/7704305 - _forwardNav: function () { - if (!this._forwardButton.disabled) { - this._updateNav(Math.min(this._navHistory.length, this._navPos + 1)); - } - }, - - // Navigate home in IFrame - _homeNav: function () { - if (!this._backButton.disabled) { - this._updateNav(1); - } - }, - - // Triggered on IFrame load, based on http://stackoverflow.com/a/7704305 - _onloadNav: function () { - if (true) { - // Force all external iframe links to open in new tab/window - // from - var as = this._iframe.contentDocument.getElementsByTagName('a'); - for (var i = 0; i < as.length; i++) { - if (L.IIPUtils.isExternal(as[i].href)) { - as[i].setAttribute('target', '_blank'); - } - } - this._iframeLoad1 = true; - } - - if (!this._navIgnore) { - var href = this._iframe.contentWindow.location.href; - if (href !== this._navHistory[this._navPos - 1]) { - this._navHistory.splice( - this._navPos, - this._navHistory.length - this._navPos - ); - this._navHistory.push(href); - this._navPos = this._navHistory.length; - this._disableNav(); - } - } else { - this._navIgnore = false; - } - }, + options: { + title: 'Documentation', + collapsed: true, + position: 'topleft', + pdflink: undefined + }, + + initialize: function (url, options) { + L.setOptions(this, options); + + this._className = 'leaflet-control-iip'; + this._id = 'leaflet-iipdoc'; + this._sideClass = 'doc'; + this._url = url; + }, + + _initDialog: function () { + var _this = this, + className = this._className, + layer = this._layer, + frameBox = L.DomUtil.create('div', + this._className + '-framebox', this._dialog), + iframe = this._iframe = L.DomUtil.create('iframe', + this._className + '-doc', frameBox); + iframe.src = this._url; + iframe.frameborder = 0; + + this._navHistory = []; + this._navPos = 0; + this._ignore = false; + + L.DomEvent.on(iframe, 'load hashchange', this._onloadNav, this); + + var box = this._addDialogBox('leaflet-iipdoc-dialog'), + line = this._addDialogLine('Navigate:', box), + elem = this._addDialogElement(line); + + this._homeButton = this._createButton(className + '-button', elem, + 'home', this._homeNav, 'Navigate home'); + this._backButton = this._createButton(className + '-button', elem, + 'back', this._backNav, 'Navigate backward'); + this._forwardButton = this._createButton(className + '-button', elem, + 'forward', this._forwardNav, 'Navigate forward'); + + if (this.options.pdflink) { + var pdfButton = this._createButton(className + '-button', elem, + 'pdf', undefined, 'Download PDF version'); + pdfButton.href = this.options.pdflink; + } + }, + + // Update navigation buttons, based on http://stackoverflow.com/a/7704305 + _updateNav: function (newPos) { + if (newPos !== this._navPos) { + this._navPos = newPos; + this._navIgnore = true; + this._iframe.src = this._navHistory[this._navPos - 1]; + this._disableNav(); + } + }, + + _disableNav: function () { + // Enable / disable back button? + this._backButton.disabled = (this._navPos === 1); + // Enable / disable forward button? + this._forwardButton.disabled = (this._navPos >= this._navHistory.length); + }, + + // Navigate back in IFrame, based on http://stackoverflow.com/a/7704305 + _backNav: function () { + if (!this._backButton.disabled) { + this._updateNav(Math.max(1, this._navPos - 1)); + } + }, + + // Navigate forward in IFrame, based on http://stackoverflow.com/a/7704305 + _forwardNav: function () { + if (!this._forwardButton.disabled) { + this._updateNav(Math.min(this._navHistory.length, this._navPos + 1)); + } + }, + + // Navigate home in IFrame + _homeNav: function () { + if (!this._backButton.disabled) { + this._updateNav(1); + } + }, + + // Triggered on IFrame load, based on http://stackoverflow.com/a/7704305 + _onloadNav: function () { + if (true) { + // Force all external iframe links to open in new tab/window + // from + var as = this._iframe.contentDocument.getElementsByTagName('a'); + for (var i = 0; i < as.length; i++) { + if (L.IIPUtils.isExternal(as[i].href)) { + as[i].setAttribute('target', '_blank'); + } + } + this._iframeLoad1 = true; + } + + if (!this._navIgnore) { + var href = this._iframe.contentWindow.location.href; + if (href !== this._navHistory[this._navPos - 1]) { + this._navHistory.splice(this._navPos, this._navHistory.length - this._navPos); + this._navHistory.push(href); + this._navPos = this._navHistory.length; + this._disableNav(); + } + } else { + this._navIgnore = false; + } + } + }); L.control.iip.doc = function (url, options) { - return new L.Control.IIP.Doc(url, options); + return new L.Control.IIP.Doc(url, options); }; + + /* # L.Control.IIP.image adjusts the basic rendering options of an IIP layer # (see http://iipimage.sourceforge.net/documentation/protocol/) @@ -5500,166 +4650,125 @@ L.control.iip.doc = function (url, options) { */ if (typeof require !== 'undefined') { - var $ = require('jquery'); + var $ = require('jquery'); } L.Control.IIP.Image = L.Control.IIP.extend({ - options: { - title: 'Image preferences', - collapsed: true, - position: 'topleft', - }, - - initialize: function (options) { - L.setOptions(this, options); - - this._className = 'leaflet-control-iip'; - this._id = 'leaflet-iipimage'; - this._sideClass = 'image'; - this._initsettings = {}; - }, - - // Copy image settings from layer - saveSettings: function (layer, settings) { - if (!settings) { - return; - } - - settings.invertCMap = layer.iipInvertCMap; - settings.contrast = layer.iipContrast; - settings.colorSat = layer.iipColorSat; - settings.gamma = layer.iipGamma; - settings.quality = layer.iipQuality; - }, - - // Copy image settings back to layer and update widget values - loadSettings: function (layer, settings) { - if (!settings) { - return; - } - - layer.iipInvertCMap = settings.invertCMap; - this._updateInput(this._input.invertCMap, settings.invertCMap); - layer.iipContrast = settings.contrast; - this._updateInput(this._input.contrast, settings.contrast); - layer.iipColorSat = settings.colorSat; - this._updateInput(this._input.colorSat, settings.colorSat); - layer.iipGamma = settings.gamma; - this._updateInput(this._input.gamma, settings.gamma); - layer.iipQuality = settings.quality; - this._updateInput(this._input.quality, settings.quality); - }, - - _initDialog: function () { - var _this = this, - className = this._className, - layer = this._layer, - map = this._map; - - // _input will contain widget instances - this._input = {}; - - // copy initial IIP image parameters from the layer object - this.saveSettings(layer, this._initsettings); - - // Invert - this._input.invertCMap = this._addSwitchInput( - layer, - this._dialog, - 'Invert:', - 'iipInvertCMap', - 'Invert color map(s)', - 'leaflet-invertCMap', - layer.iipInvertCMap - ); - - // Contrast - this._input.contrast = this._addNumericalInput( - layer, - this._dialog, - 'Contrast:', - 'iipContrast', - 'Adjust Contrast. 1.0: normal.', - 'leaflet-contrastValue', - layer.iipContrast, - 0.05, - 0.0, - 10.0 - ); - - // Colour saturation - this._input.colorSat = this._addNumericalInput( - layer, - this._dialog, - 'Color Sat.:', - 'iipColorSat', - 'Adjust Color Saturation. 0: B&W, 1.0: normal.', - 'leaflet-colorsatvalue', - layer.iipColorSat, - 0.05, - 0.0, - 5.0, - this._updateMix - ); - - // Gamma - this._input.gamma = this._addNumericalInput( - layer, - this._dialog, - 'Gamma:', - 'iipGamma', - 'Adjust Gamma correction. The standard value is 2.2.', - 'leaflet-gammavalue', - layer.iipGamma, - 0.05, - 0.5, - 5.0 - ); - - // JPEG quality - this._input.quality = this._addNumericalInput( - layer, - this._dialog, - 'JPEG quality:', - 'iipQuality', - 'Adjust JPEG compression quality. 1: lowest, 100: highest', - 'leaflet-qualvalue', - layer.iipQuality, - 1, - 1, - 100 - ); - - // Reset settings button - var line = this._addDialogLine('Reset:', this._dialog), - elem = this._addDialogElement(line); - - this._createButton( - className + '-button', - elem, - 'image-reset', - function () { - _this.loadSettings(layer, _this._initsettings); - layer.updateMix(); - layer.redraw(); - }, - 'Reset image settings' - ); - }, - - _updateMix: function (layer) { - var nchannel = layer.iipNChannel; - for (var c = 0; c < nchannel; c++) { - layer.rgbToMix(c); - } - return; - }, + options: { + title: 'Image preferences', + collapsed: true, + position: 'topleft' + }, + + initialize: function (options) { + L.setOptions(this, options); + + this._className = 'leaflet-control-iip'; + this._id = 'leaflet-iipimage'; + this._sideClass = 'image'; + this._initsettings = {}; + }, + + // Copy image settings from layer + saveSettings: function (layer, settings) { + if (!settings) { + return; + } + + settings.invertCMap = layer.iipInvertCMap; + settings.contrast = layer.iipContrast; + settings.colorSat = layer.iipColorSat; + settings.gamma = layer.iipGamma; + settings.quality = layer.iipQuality; + }, + + // Copy image settings back to layer and update widget values + loadSettings: function (layer, settings) { + if (!settings) { + return; + } + + layer.iipInvertCMap = settings.invertCMap; + this._updateInput(this._input.invertCMap, settings.invertCMap); + layer.iipContrast = settings.contrast; + this._updateInput(this._input.contrast, settings.contrast); + layer.iipColorSat = settings.colorSat; + this._updateInput(this._input.colorSat, settings.colorSat); + layer.iipGamma = settings.gamma; + this._updateInput(this._input.gamma, settings.gamma); + layer.iipQuality = settings.quality; + this._updateInput(this._input.quality, settings.quality); + }, + + _initDialog: function () { + var _this = this, + className = this._className, + layer = this._layer, + map = this._map; + + // _input will contain widget instances + this._input = {}; + + // copy initial IIP image parameters from the layer object + this.saveSettings(layer, this._initsettings); + + // Invert + this._input.invertCMap = this._addSwitchInput(layer, this._dialog, + 'Invert:', 'iipInvertCMap', + 'Invert color map(s)', 'leaflet-invertCMap', layer.iipInvertCMap); + + // Contrast + this._input.contrast = this._addNumericalInput(layer, + this._dialog, 'Contrast:', 'iipContrast', + 'Adjust Contrast. 1.0: normal.', 'leaflet-contrastValue', + layer.iipContrast, 0.05, 0.0, 10.0); + + // Colour saturation + this._input.colorSat = this._addNumericalInput(layer, + this._dialog, 'Color Sat.:', 'iipColorSat', + 'Adjust Color Saturation. 0: B&W, 1.0: normal.', 'leaflet-colorsatvalue', + layer.iipColorSat, 0.05, 0.0, 5.0, this._updateMix); + + // Gamma + this._input.gamma = this._addNumericalInput(layer, + this._dialog, 'Gamma:', 'iipGamma', + 'Adjust Gamma correction. The standard value is 2.2.', + 'leaflet-gammavalue', layer.iipGamma, 0.05, 0.5, 5.0); + + // JPEG quality + this._input.quality = this._addNumericalInput(layer, + this._dialog, 'JPEG quality:', 'iipQuality', + 'Adjust JPEG compression quality. 1: lowest, 100: highest', + 'leaflet-qualvalue', layer.iipQuality, 1, 1, 100); + + // Reset settings button + var line = this._addDialogLine('Reset:', this._dialog), + elem = this._addDialogElement(line); + + this._createButton(className + '-button', elem, 'image-reset', function () { + _this.loadSettings(layer, _this._initsettings); + layer.updateMix(); + layer.redraw(); + }, 'Reset image settings'); + + }, + + _updateMix: function (layer) { + var nchannel = layer.iipNChannel; + for (var c = 0; c < nchannel; c++) { + layer.rgbToMix(c); + } + return; + } + }); L.control.iip.image = function (options) { - return new L.Control.IIP.Image(options); + return new L.Control.IIP.Image(options); }; + + /* # L.Control.IIP.Profile manages image profile diagrams # @@ -5672,449 +4781,389 @@ L.control.iip.image = function (options) { */ if (typeof require !== 'undefined') { - var $ = require('jquery'); + var $ = require('jquery'); } L.Control.IIP.Profile = L.Control.IIP.extend({ - options: { - title: 'Profile overlays', - collapsed: true, - position: 'topleft', - profile: true, - profileColor: '#FF00FF', - spectrum: true, - spectrumColor: '#A000FF', - }, - - initialize: function (options) { - L.setOptions(this, options); - this._className = 'leaflet-control-iip'; - this._id = 'leaflet-iipprofile'; - this._layers = {}; - this._sideClass = 'profile'; - this._handlingClick = false; - }, - - _initDialog: function () { - var _this = this, - options = this.options, - className = this._className, - box = this._addDialogBox(), - line, - elem; - - if (options.profile) { - line = this._addDialogLine('Profile:', box); - elem = this._addDialogElement(line); - var linecolpick = this._createColorPicker( - className + '-color', - elem, - 'profile', - options.profileColor, - false, - 'iipProfile', - 'Click to set line color' - ); - - // Create start profile line button - this._createButton( - className + '-button', - elem, - 'start', - function () { - if (this._currProfileLine) { - this._updateLine(); - } else { - var map = _this._map, - point = map.getCenter(), - line = (this._currProfileLine = L.polyline([point, point], { - color: linecolpick.value, - weight: 7, - opacity: 0.5, - })); - line.nameColor = linecolpick.value; - line.addTo(map); - map.on('drag', this._updateLine, this); - } - }, - 'Start drawing a profile line' - ); - - // Create end profile line button - this._createButton( - className + '-button', - elem, - 'end', - this._profileEnd, - 'End line and plot' - ); - } - - if (options.spectrum) { - // Create Spectrum dialog line - line = this._addDialogLine('Spectrum:', box); - elem = this._addDialogElement(line); - - // Create Spectrum color picker - var speccolpick = this._createColorPicker( - className + '-color', - elem, - 'spectrum', - options.spectrumColor, - false, - 'iipSpectra', - 'Click to set marker color' - ); - - // Create Spectrum button - this._createButton( - className + '-button', - elem, - 'spectrum', - function () { - var map = _this._map, - latLng = map.getCenter(), - zoom = map.options.crs.options.nzoom - 1, - point = map - .project(latLng, zoom) - .floor() - .add([0.5, 0.5]), - rLatLng = map.unproject(point, zoom), - marker = (this._spectrumMarker = L.circleMarker(rLatLng, { - color: speccolpick.value, - radius: 6, - title: 'Spectrum', - }).addTo(map)), - popdiv = L.DomUtil.create('div', this._className + '-popup'), - activity = L.DomUtil.create( - 'div', - this._className + '-activity', - popdiv - ); - - popdiv.id = 'leaflet-spectrum-plot'; - marker - .bindPopup(popdiv, { - minWidth: 16, - maxWidth: 1024, - closeOnClick: false, - }) - .openPopup(); - L.IIPUtils.requestURL( - this._layer._url.replace(/\&.*$/g, '') + - '&PFL=' + - zoom.toString() + - ':' + - (point.x - 0.5).toFixed(0) + - ',' + - (point.y - 0.5).toFixed(0) + - '-' + - (point.x - 0.5).toFixed(0) + - ',' + - (point.y - 0.5).toFixed(0), - 'getting IIP layer spectrum', - this._plotSpectrum, - this - ); - }, - 'Plot a spectrum at the current map position' - ); - } - }, - - _updateLine: function (e) { - var map = this._map, - latLng = map.getCenter(), - maxzoom = map.options.crs.options.nzoom - 1, - path = this._currProfileLine.getLatLngs(), - point1 = map.project(path[0], maxzoom), - point2 = map.project(map.getCenter(), maxzoom); - if (Math.abs(point1.x - point2.x) > Math.abs(point1.y - point2.y)) { - point2.y = point1.y; - } else { - point2.x = point1.x; - } - - path[1] = map.unproject(point2, maxzoom); - this._currProfileLine.redraw(); - }, - - _profileEnd: function () { - var map = this._map, - point = map.getCenter(), - line = (this._profileLine = this._currProfileLine); - - map.off('drag', this._updateLine, this); - this._currProfileLine = undefined; - - var popdiv = L.DomUtil.create('div', this._className + '-popup'), - activity = L.DomUtil.create('div', this._className + '-activity', popdiv); - - popdiv.id = 'leaflet-profile-plot'; - line - .bindPopup(popdiv, { minWidth: 16, maxWidth: 1024, closeOnClick: false }) - .openPopup(); - var zoom = map.options.crs.options.nzoom - 1, - path = line.getLatLngs(), - point1 = map.project(path[0], zoom), - point2 = map.project(path[1], zoom), - x, - y; - - if (point2.x < point1.x) { - x = point2.x; - point2.x = point1.x; - point1.x = x; - } - if (point2.y < point1.y) { - y = point2.y; - point2.y = point1.y; - point1.y = y; - } - - L.IIPUtils.requestURL( - this._layer._url.replace(/\&.*$/g, '') + - '&PFL=' + - zoom.toString() + - ':' + - (point1.x - 0.5).toFixed(0) + - ',' + - (point1.y - 0.5).toFixed(0) + - '-' + - (point2.x - 0.5).toFixed(0) + - ',' + - (point2.y - 0.5).toFixed(0), - 'getting IIP layer profile', - this._plotProfile, - this - ); - }, - - _getMeasurementString: function () { - var currentLatLng = this._currentLatLng, - previousLatLng = this._markers[this._markers.length - 1].getLatLng(), - distance, - distanceStr, - unit; - - // calculate the distance from the last fixed point to the mouse position - distance = - this._measurementRunningTotal + - L.IIPUtils.distance(currentLatLng, previousLatLng); - - if (distance >= 1.0) { - unit = '°'; - } else { - distance *= 60.0; - if (distance >= 1.0) { - unit = '''; - } else { - distance *= 60.0; - unit = '"'; - } - } - distanceStr = distance.toFixed(2) + unit; - - return distanceStr; - }, - - _plotProfile: function (self, httpRequest) { - if (httpRequest.readyState === 4) { - if (httpRequest.status === 200) { - var json = JSON.parse(httpRequest.responseText), - rawprof = json.profile, - layer = self._layer, - line = self._profileLine, - popdiv = document.getElementById('leaflet-profile-plot'), - prof = [], - series = [], - title, - ylabel; - - self.addLayer(line, 'Image profile'); - - if (layer.iipMode === 'mono') { - prof.push(self._extractProfile(layer, rawprof, layer.iipChannel)); - series.push({ - color: 'black', - }); - title = - 'Image profile for ' + layer.iipChannelLabels[layer.iipChannel]; - ylabel = 'Pixel value in ' + layer.iipChannelUnits[layer.iipChannel]; - } else { - var rgb = layer.iipRGB; - for (var chan = 0; chan < layer.iipNChannel; chan++) { - if (rgb[chan].isOn()) { - prof.push(self._extractProfile(layer, rawprof, chan)); - series.push({ - color: rgb[chan].toStr(), - label: layer.iipChannelLabels[chan], - }); - } - } - title = 'Image profiles'; - ylabel = 'Pixel value'; - } - - $(document).ready(function () { - $.jqplot.config.enablePlugins = true; - $.jqplot('leaflet-profile-plot', prof, { - title: title, - grid: { - backgroundColor: '#ddd', - gridLineColor: '#eee', - }, - axes: { - xaxis: { - label: 'position along line', - labelRenderer: $.jqplot.CanvasAxisLabelRenderer, - pad: 1.0, - }, - yaxis: { - label: ylabel, - labelRenderer: $.jqplot.CanvasAxisLabelRenderer, - pad: 1.0, - }, - }, - legend: { - show: layer.iipMode !== 'mono', - location: 'ne', - }, - highlighter: { - show: true, - sizeAdjust: 2, - tooltipLocation: 'n', - tooltipAxes: 'y', - tooltipFormatString: - '%.6g ' + layer.iipChannelUnits[layer.iipChannel], - useAxesFormatters: false, - bringSeriesToFront: true, - }, - cursor: { - show: true, - zoom: true, - }, - series: series, - seriesDefaults: { - lineWidth: 2.0, - showMarker: false, - }, - }); - }); - - popdiv.removeChild(popdiv.childNodes[0]); // Remove activity spinner - - line._popup.update(); // TODO: avoid private method - } - } - }, - - // Extract the image profile in a given channel - _extractProfile: function (layer, rawprof, chan) { - var prof = [], - nchan = layer.iipNChannel, - npix = rawprof.length / nchan; - - for (var i = 0; i < npix; i++) { - prof.push(rawprof[i * nchan + chan]); - } - - return prof; - }, - - _plotSpectrum: function (self, httpRequest) { - if (httpRequest.readyState === 4) { - if (httpRequest.status === 200) { - var json = JSON.parse(httpRequest.responseText), - rawprof = json.profile, - layer = self._layer, - marker = self._spectrumMarker, - popdiv = document.getElementById('leaflet-spectrum-plot'), - spec = [], - series = [], - title, - ylabel; - self.addLayer(marker, 'Image spectrum'); - - for (var chan = 0; chan < layer.iipNChannel; chan++) { - spec.push([ - layer.iipChannelLabels[chan], - self._extractAverage(layer, rawprof, chan), - ]); - } - title = 'Image Spectrum'; - ylabel = 'Average pixel value'; - $(document).ready(function () { - $.jqplot.config.enablePlugins = true; - $.jqplot('leaflet-spectrum-plot', [spec], { - title: title, - grid: { - backgroundColor: '#F0F0F0', - gridLineColor: '#F8F8F8', - }, - axes: { - xaxis: { - renderer: $.jqplot.CategoryAxisRenderer, - tickRenderer: $.jqplot.CanvasAxisTickRenderer, - tickOptions: { - angle: -30, - fontSize: '6pt', - }, - }, - yaxis: { - label: ylabel, - labelRenderer: $.jqplot.CanvasAxisLabelRenderer, - }, - }, - highlighter: { - show: true, - sizeAdjust: 2, - tooltipLocation: 'n', - tooltipAxes: 'y', - tooltipFormatString: - '%.6g ' + layer.iipChannelUnits[layer.iipChannel], - useAxesFormatters: false, - }, - cursor: { - show: true, - zoom: true, - }, - seriesDefaults: { - lineWidth: 2.0, - showMarker: false, - }, - }); - }); - - popdiv.removeChild(popdiv.childNodes[0]); // Remove activity spinner - - marker._popup.update(); // TODO: avoid private method - } - } - }, - - // Extract the average of a series of pixels in a given channel - _extractAverage: function (layer, rawprof, chan) { - var nchan = layer.iipNChannel, - npix = rawprof.length / nchan, - val = 0.0; - - if (npix === 0) { - return 0.0; - } - - for (var i = 0; i < npix; i++) { - val += rawprof[i * nchan + chan]; - } - - return val / npix; - }, + + options: { + title: 'Profile overlays', + collapsed: true, + position: 'topleft', + profile: true, + profileColor: '#FF00FF', + spectrum: true, + spectrumColor: '#A000FF' + }, + + initialize: function (options) { + L.setOptions(this, options); + this._className = 'leaflet-control-iip'; + this._id = 'leaflet-iipprofile'; + this._layers = {}; + this._sideClass = 'profile'; + this._handlingClick = false; + }, + + _initDialog: function () { + var _this = this, + options = this.options, + className = this._className, + box = this._addDialogBox(), + line, elem; + + if (options.profile) { + line = this._addDialogLine('Profile:', box); + elem = this._addDialogElement(line); + var linecolpick = this._createColorPicker( + className + '-color', + elem, + 'profile', + options.profileColor, + false, + 'iipProfile', + 'Click to set line color' + ); + + // Create start profile line button + this._createButton(className + '-button', elem, 'start', function () { + if (this._currProfileLine) { + this._updateLine(); + } else { + var map = _this._map, + point = map.getCenter(), + line = this._currProfileLine = L.polyline([point, point], { + color: linecolpick.value, + weight: 7, + opacity: 0.5 + }); + line.nameColor = linecolpick.value; + line.addTo(map); + map.on('drag', this._updateLine, this); + } + }, 'Start drawing a profile line'); + + // Create end profile line button + this._createButton(className + '-button', elem, 'end', + this._profileEnd, 'End line and plot'); + } + + if (options.spectrum) { + // Create Spectrum dialog line + line = this._addDialogLine('Spectrum:', box); + elem = this._addDialogElement(line); + + // Create Spectrum color picker + var speccolpick = this._createColorPicker( + className + '-color', + elem, + 'spectrum', + options.spectrumColor, + false, + 'iipSpectra', + 'Click to set marker color' + ); + + // Create Spectrum button + this._createButton(className + '-button', elem, 'spectrum', function () { + var map = _this._map, + latLng = map.getCenter(), + zoom = map.options.crs.options.nzoom - 1, + point = map.project(latLng, zoom).floor().add([0.5, 0.5]), + rLatLng = map.unproject(point, zoom), + marker = this._spectrumMarker = L.circleMarker(rLatLng, { + color: speccolpick.value, + radius: 6, + title: 'Spectrum' + }).addTo(map), + popdiv = L.DomUtil.create('div', this._className + '-popup'), + activity = L.DomUtil.create('div', this._className + '-activity', popdiv); + + popdiv.id = 'leaflet-spectrum-plot'; + marker.bindPopup(popdiv, + { minWidth: 16, maxWidth: 1024, closeOnClick: false }).openPopup(); + L.IIPUtils.requestURL(this._layer._url.replace(/\&.*$/g, '') + + '&PFL=' + zoom.toString() + ':' + + (point.x - 0.5).toFixed(0) + ',' + (point.y - 0.5).toFixed(0) + '-' + + (point.x - 0.5).toFixed(0) + ',' + (point.y - 0.5).toFixed(0), + 'getting IIP layer spectrum', this._plotSpectrum, this); + }, 'Plot a spectrum at the current map position'); + } + }, + + _updateLine: function (e) { + var map = this._map, + latLng = map.getCenter(), + maxzoom = map.options.crs.options.nzoom - 1, + path = this._currProfileLine.getLatLngs(), + point1 = map.project(path[0], maxzoom), + point2 = map.project(map.getCenter(), maxzoom); + if (Math.abs(point1.x - point2.x) > Math.abs(point1.y - point2.y)) { + point2.y = point1.y; + } else { + point2.x = point1.x; + } + + path[1] = map.unproject(point2, maxzoom); + this._currProfileLine.redraw(); + }, + + _profileEnd: function () { + var map = this._map, + point = map.getCenter(), + line = this._profileLine = this._currProfileLine; + + map.off('drag', this._updateLine, this); + this._currProfileLine = undefined; + + var popdiv = L.DomUtil.create('div', this._className + '-popup'), + activity = L.DomUtil.create('div', this._className + '-activity', popdiv); + + popdiv.id = 'leaflet-profile-plot'; + line.bindPopup(popdiv, + { minWidth: 16, maxWidth: 1024, closeOnClick: false }).openPopup(); + var zoom = map.options.crs.options.nzoom - 1, + path = line.getLatLngs(), + point1 = map.project(path[0], zoom), + point2 = map.project(path[1], zoom), + x, y; + + if (point2.x < point1.x) { + x = point2.x; + point2.x = point1.x; + point1.x = x; + } + if (point2.y < point1.y) { + y = point2.y; + point2.y = point1.y; + point1.y = y; + } + + L.IIPUtils.requestURL(this._layer._url.replace(/\&.*$/g, '') + + '&PFL=' + zoom.toString() + ':' + (point1.x - 0.5).toFixed(0) + ',' + + (point1.y - 0.5).toFixed(0) + '-' + (point2.x - 0.5).toFixed(0) + ',' + + (point2.y - 0.5).toFixed(0), + 'getting IIP layer profile', + this._plotProfile, this); + }, + + _getMeasurementString: function () { + var currentLatLng = this._currentLatLng, + previousLatLng = this._markers[this._markers.length - 1].getLatLng(), + distance, distanceStr, unit; + + // calculate the distance from the last fixed point to the mouse position + distance = this._measurementRunningTotal + L.IIPUtils.distance(currentLatLng, previousLatLng); + + if (distance >= 1.0) { + unit = '°'; + } else { + distance *= 60.0; + if (distance >= 1.0) { + unit = '''; + } else { + distance *= 60.0; + unit = '"'; + } + } + distanceStr = distance.toFixed(2) + unit; + + return distanceStr; + }, + + _plotProfile: function (self, httpRequest) { + if (httpRequest.readyState === 4) { + if (httpRequest.status === 200) { + var json = JSON.parse(httpRequest.responseText), + rawprof = json.profile, + layer = self._layer, + line = self._profileLine, + popdiv = document.getElementById('leaflet-profile-plot'), + prof = [], + series = [], + title, ylabel; + + self.addLayer(line, 'Image profile'); + + if (layer.iipMode === 'mono') { + prof.push(self._extractProfile(layer, rawprof, layer.iipChannel)); + series.push({ + color: 'black', + }); + title = 'Image profile for ' + layer.iipChannelLabels[layer.iipChannel]; + ylabel = 'Pixel value in ' + layer.iipChannelUnits[layer.iipChannel]; + } else { + var rgb = layer.iipRGB; + for (var chan = 0; chan < layer.iipNChannel; chan++) { + if (rgb[chan].isOn()) { + prof.push(self._extractProfile(layer, rawprof, chan)); + series.push({ + color: rgb[chan].toStr(), + label: layer.iipChannelLabels[chan] + }); + } + } + title = 'Image profiles'; + ylabel = 'Pixel value'; + } + + $(document).ready(function () { + $.jqplot.config.enablePlugins = true; + $.jqplot('leaflet-profile-plot', prof, { + title: title, + grid: { + backgroundColor: '#ddd', + gridLineColor: '#eee' + }, + axes: { + xaxis: { + label: 'position along line', + labelRenderer: $.jqplot.CanvasAxisLabelRenderer, + pad: 1.0 + }, + yaxis: { + label: ylabel, + labelRenderer: $.jqplot.CanvasAxisLabelRenderer, + pad: 1.0 + } + }, + legend: { + show: (layer.iipMode !== 'mono'), + location: 'ne', + }, + highlighter: { + show: true, + sizeAdjust: 2, + tooltipLocation: 'n', + tooltipAxes: 'y', + tooltipFormatString: '%.6g ' + layer.iipChannelUnits[layer.iipChannel], + useAxesFormatters: false, + bringSeriesToFront: true + }, + cursor: { + show: true, + zoom: true + }, + series: series, + seriesDefaults: { + lineWidth: 2.0, + showMarker: false + } + }); + }); + + popdiv.removeChild(popdiv.childNodes[0]); // Remove activity spinner + + line._popup.update(); // TODO: avoid private method + } + } + }, + + // Extract the image profile in a given channel + _extractProfile: function (layer, rawprof, chan) { + var prof = [], + nchan = layer.iipNChannel, + npix = rawprof.length / nchan; + + for (var i = 0; i < npix; i++) { + prof.push(rawprof[i * nchan + chan]); + } + + return prof; + }, + + _plotSpectrum: function (self, httpRequest) { + if (httpRequest.readyState === 4) { + if (httpRequest.status === 200) { + var json = JSON.parse(httpRequest.responseText), + rawprof = json.profile, + layer = self._layer, + marker = self._spectrumMarker, + popdiv = document.getElementById('leaflet-spectrum-plot'), + spec = [], + series = [], + title, ylabel; + self.addLayer(marker, 'Image spectrum'); + + for (var chan = 0; chan < layer.iipNChannel; chan++) { + spec.push([ + layer.iipChannelLabels[chan], + self._extractAverage(layer, rawprof, chan) + ]); + } + title = 'Image Spectrum'; + ylabel = 'Average pixel value'; + $(document).ready(function () { + $.jqplot.config.enablePlugins = true; + $.jqplot('leaflet-spectrum-plot', [spec], { + title: title, + grid: { + backgroundColor: '#F0F0F0', + gridLineColor: '#F8F8F8' + }, + axes: { + xaxis: { + renderer: $.jqplot.CategoryAxisRenderer, + tickRenderer: $.jqplot.CanvasAxisTickRenderer, + tickOptions: { + angle: -30, + fontSize: '6pt' + } + }, + yaxis: { + label: ylabel, + labelRenderer: $.jqplot.CanvasAxisLabelRenderer, + } + }, + highlighter: { + show: true, + sizeAdjust: 2, + tooltipLocation: 'n', + tooltipAxes: 'y', + tooltipFormatString: '%.6g ' + layer.iipChannelUnits[layer.iipChannel], + useAxesFormatters: false + }, + cursor: { + show: true, + zoom: true + }, + seriesDefaults: { + lineWidth: 2.0, + showMarker: false + } + }); + }); + + popdiv.removeChild(popdiv.childNodes[0]); // Remove activity spinner + + marker._popup.update(); // TODO: avoid private method + } + } + }, + + // Extract the average of a series of pixels in a given channel + _extractAverage: function (layer, rawprof, chan) { + var nchan = layer.iipNChannel, + npix = rawprof.length / nchan, + val = 0.0; + + if (npix === 0) { return 0.0; } + + for (var i = 0; i < npix; i++) { + val += rawprof[i * nchan + chan]; + } + + return val / npix; + } + }); L.control.iip.profile = function (options) { - return new L.Control.IIP.Profile(options); + return new L.Control.IIP.Profile(options); }; + + /* # L.Control.IIP.Regions manages overlays of regions or points of interest # @@ -6127,179 +5176,162 @@ L.control.iip.profile = function (options) { */ L.Control.IIP.Region = L.Control.IIP.extend({ - options: { - title: 'Region overlays', - collapsed: true, - position: 'topleft', - nativeCelsys: true, - color: '#00FFFF', - timeOut: 30, // seconds - }, - - initialize: function (regions, options) { - // Regions is an array of {url, name [, description]} objects - L.setOptions(this, options); - this._className = 'leaflet-control-iip'; - this._id = 'leaflet-iipregion'; - this._layers = {}; - this._handlingClick = false; - this._sideClass = 'region'; - this._regions = regions && regions[0] ? regions : []; - }, - - _initDialog: function () { - var className = this._className, - regions = this._regions, - box = this._addDialogBox(), - line = this._addDialogLine('Regions:', box), - elem = this._addDialogElement(line), - colpick = this._createColorPicker( - className + '-color', - elem, - 'region', - this.options.color, - false, - 'iipRegion', - 'Click to set region color' - ); - - var select = (this._regionSelect = this._createSelectMenu( - this._className + '-select', - elem, - regions.map(function (o) { - return o.name; - }), - regions.map(function (o) { - return o.load ? true : false; - }), - -1, - undefined, - 'Select region' - )); - - elem = this._addDialogElement(line); - this._createButton( - className + '-button', - elem, - 'region', - function () { - var index = select.selectedIndex - 1; // Ignore 'Choose region' entry - if (index >= 0) { - var region = this._regions[index]; - region.color = colpick.value; - select.selectedIndex = 0; - select.opt[index].disabled = true; - this._getRegion(region, this.options.timeOut); - } - }, - 'Display region' - ); - - // Load regions that have the 'load' option set. - var region; - for (var index = 0; index < regions.length; index++) { - region = regions[index]; - region.index = index; - if (region.load === true) { - if (!region.color) { - region.color = this.options.color; - } - this._getRegion(regions[index], this.options.timeOut); - } - } - }, - - _resetDialog: function () { - // Do nothing: no need to reset with layer changes - }, - - _getRegion: function (region, timeout) { - var _this = this, - map = this._map, - wcs = map.options.crs, - sysflag = wcs.forceNativeCelsys && !this.options.nativeCelsys, - templayer = new L.LayerGroup(null); - - // Add a temporary "dummy" layer to activate a spinner sign - templayer.notReady = true; - this.addLayer(templayer, region.name); - - L.IIPUtils.requestURL( - region.url, - 'loading ' + region.name + ' data', - function (context, httpRequest) { - _this._loadRegion(region, templayer, context, httpRequest); - }, - this, - this.options.timeOut - ); - }, - - _loadRegion: function (region, templayer, _this, httpRequest) { - if (httpRequest.readyState === 4) { - if (httpRequest.status === 200) { - var wcs = _this._map.options.crs, - response = httpRequest.responseText, - geoRegion = L.geoJson(JSON.parse(response), { - onEachFeature: function (feature, layer) { - if (feature.properties && feature.properties.description) { - layer.bindPopup(feature.properties.description); - } else if (region.description) { - layer.bindPopup(region.description); - } - }, - coordsToLatLng: function (coords) { - if (wcs.forceNativeCelsys) { - var latLng = wcs.eqToCelsys(L.latLng(coords[1], coords[0])); - return new L.LatLng(latLng.lat, latLng.lng, coords[2]); - } else { - return new L.LatLng(coords[1], coords[0], coords[2]); - } - }, - style: function (feature) { - return { color: region.color, weight: 2 }; - }, - pointToLayer: function (feature, latlng) { - return region.drawPoint - ? region.drawPoint(feature, latlng) - : L.marker(latlng); - }, - }); - geoRegion.nameColor = region.color; - geoRegion.addTo(_this._map); - _this.removeLayer(templayer); - _this.addLayer(geoRegion, region.name, region.index); - L.DomEvent.on( - geoRegion, - 'trash', - function (e) { - if (e.index || e.index === 0) { - _this._regionSelect.opt[e.index].disabled = false; - } - }, - _this - ); - } else { - if (httpRequest.status !== 0) { - alert( - 'Error ' + - httpRequest.status + - ' while downloading ' + - region.url + - '.' - ); - } - _this.removeLayer(templayer); - _this._regionSelect.opt[region.index].disabled = false; - } - } - }, + + options: { + title: 'Region overlays', + collapsed: true, + position: 'topleft', + nativeCelsys: true, + color: '#00FFFF', + timeOut: 30 // seconds + }, + + initialize: function (regions, options) { + // Regions is an array of {url, name [, description]} objects + L.setOptions(this, options); + this._className = 'leaflet-control-iip'; + this._id = 'leaflet-iipregion'; + this._layers = {}; + this._handlingClick = false; + this._sideClass = 'region'; + this._regions = regions && regions[0] ? regions : []; + }, + + _initDialog: function () { + var className = this._className, + regions = this._regions, + box = this._addDialogBox(), + line = this._addDialogLine('Regions:', box), + elem = this._addDialogElement(line), + colpick = this._createColorPicker( + className + '-color', + elem, + 'region', + this.options.color, + false, + 'iipRegion', + 'Click to set region color' + ); + + var select = this._regionSelect = this._createSelectMenu( + this._className + '-select', + elem, + regions.map(function (o) { return o.name; }), + regions.map(function (o) { return (o.load ? true : false); }), + -1, + undefined, + 'Select region' + ); + + elem = this._addDialogElement(line); + this._createButton(className + '-button', + elem, + 'region', + function () { + var index = select.selectedIndex - 1; // Ignore 'Choose region' entry + if (index >= 0) { + var region = this._regions[index]; + region.color = colpick.value; + select.selectedIndex = 0; + select.opt[index].disabled = true; + this._getRegion(region, this.options.timeOut); + } + }, + 'Display region' + ); + + // Load regions that have the 'load' option set. + var region; + for (var index = 0; index < regions.length; index++) { + region = regions[index]; + region.index = index; + if (region.load === true) { + if (!region.color) { + region.color = this.options.color; + } + this._getRegion(regions[index], this.options.timeOut); + } + } + }, + + _resetDialog: function () { + // Do nothing: no need to reset with layer changes + }, + + _getRegion: function (region, timeout) { + var _this = this, + map = this._map, + wcs = map.options.crs, + sysflag = wcs.forceNativeCelsys && !this.options.nativeCelsys, + templayer = new L.LayerGroup(null); + + // Add a temporary "dummy" layer to activate a spinner sign + templayer.notReady = true; + this.addLayer(templayer, region.name); + + L.IIPUtils.requestURL(region.url, 'loading ' + region.name + ' data', + function (context, httpRequest) { + _this._loadRegion(region, templayer, context, httpRequest); + }, this, this.options.timeOut); + }, + + _loadRegion: function (region, templayer, _this, httpRequest) { + if (httpRequest.readyState === 4) { + if (httpRequest.status === 200) { + var wcs = _this._map.options.crs, + response = httpRequest.responseText, + geoRegion = L.geoJson(JSON.parse(response), { + onEachFeature: function (feature, layer) { + if (feature.properties && feature.properties.description) { + layer.bindPopup(feature.properties.description); + } else if (region.description) { + layer.bindPopup(region.description); + } + }, + coordsToLatLng: function (coords) { + if (wcs.forceNativeCelsys) { + var latLng = wcs.eqToCelsys(L.latLng(coords[1], coords[0])); + return new L.LatLng(latLng.lat, latLng.lng, coords[2]); + } else { + return new L.LatLng(coords[1], coords[0], coords[2]); + } + }, + style: function (feature) { + return { color: region.color, weight: 2 }; + }, + pointToLayer: function (feature, latlng) { + return region.drawPoint ? + region.drawPoint(feature, latlng) : L.marker(latlng); + } + }); + geoRegion.nameColor = region.color; + geoRegion.addTo(_this._map); + _this.removeLayer(templayer); + _this.addLayer(geoRegion, region.name, region.index); + L.DomEvent.on(geoRegion, 'trash', function (e) { + if (e.index || e.index === 0) { + _this._regionSelect.opt[e.index].disabled = false; + } + }, _this); + } else { + if (httpRequest.status !== 0) { + alert('Error ' + httpRequest.status + ' while downloading ' + + region.url + '.'); + } + _this.removeLayer(templayer); + _this._regionSelect.opt[region.index].disabled = false; + } + } + } + }); L.control.iip.region = function (regions, options) { - return new L.Control.IIP.Region(regions, options); + return new L.Control.IIP.Region(regions, options); }; + + /* # L.Control.IIP.snapshot offers several options to take snapshots of the current image/field # This file part of: VisiOmatic @@ -6311,125 +5343,104 @@ L.control.iip.region = function (regions, options) { */ if (typeof require !== 'undefined') { - var $ = require('jquery'), - html2canvas = require('html2canvas'); + var $ = require('jquery'), + html2canvas = require('html2canvas'); } L.Control.IIP.Snapshot = L.Control.IIP.extend({ - options: { - title: 'Field snapshot', - collapsed: true, - position: 'topleft', - }, - - initialize: function (options) { - L.setOptions(this, options); - - this._className = 'leaflet-control-iip'; - this._id = 'leaflet-iipsnapshot'; - this._sideClass = 'snapshot'; - }, - - _initDialog: function () { - var _this = this, - className = this._className, - layer = this._layer, - map = this._map; - - // Image snapshot - var line = this._addDialogLine('Snap:', this._dialog), - elem = this._addDialogElement(line), - items = ['Screen pixels', 'Native pixels']; - - this._snapType = 0; - this._snapSelect = this._createSelectMenu( - this._className + '-select', - elem, - items, - undefined, - this._snapType, - function () { - this._snapType = parseInt(this._snapSelect.selectedIndex - 1, 10); - }, - 'Select snapshot resolution' - ); - - var hiddenlink = document.createElement('a'), - button = this._createButton( - className + '-button', - elem, - 'snapshot', - function (event) { - var latlng = map.getCenter(), - bounds = map.getPixelBounds(), - z = map.getZoom(), - zfac; - - if (z > layer.iipMaxZoom) { - zfac = Math.pow(2, z - layer.iipMaxZoom); - z = layer.iipMaxZoom; - } else { - zfac = 1; - } - - var sizex = layer.iipImageSize[z].x * zfac, - sizey = layer.iipImageSize[z].y * zfac, - dx = bounds.max.x - bounds.min.x, - dy = bounds.max.y - bounds.min.y; - - hiddenlink.href = layer - .getTileUrl({ x: 1, y: 1 }) - .replace( - /JTL\=\d+\,\d+/g, - 'RGN=' + - bounds.min.x / sizex + - ',' + - bounds.min.y / sizey + - ',' + - dx / sizex + - ',' + - dy / sizey + - '&WID=' + - (this._snapType === 0 - ? Math.floor(dx / zfac) - : Math.floor(dx / zfac / layer.wcs.scale(z))) + - '&CVT=jpeg' - ); - hiddenlink.download = - layer._title + - '_' + - L.IIPUtils.latLngToHMSDMS(latlng).replace(/[\s\:\.]/g, '') + - '.jpg'; - hiddenlink.click(); - }, - 'Take a snapshot of the displayed image' - ); - - document.body.appendChild(hiddenlink); - - line = this._addDialogLine('Print:', this._dialog); - elem = this._addDialogElement(line); - button = this._createButton( - className + '-button', - elem, - 'print', - function (event) { - var control = document.querySelector( - '#map > .leaflet-control-container' - ); - control.style.display = 'none'; - window.print(); - control.style.display = 'unset'; - }, - 'Print current map' - ); - }, + options: { + title: 'Field snapshot', + collapsed: true, + position: 'topleft' + }, + + initialize: function (options) { + L.setOptions(this, options); + + this._className = 'leaflet-control-iip'; + this._id = 'leaflet-iipsnapshot'; + this._sideClass = 'snapshot'; + }, + + _initDialog: function () { + var _this = this, + className = this._className, + layer = this._layer, + map = this._map; + + // Image snapshot + var line = this._addDialogLine('Snap:', this._dialog), + elem = this._addDialogElement(line), + items = ['Screen pixels', 'Native pixels']; + + this._snapType = 0; + this._snapSelect = this._createSelectMenu( + this._className + '-select', + elem, + items, + undefined, + this._snapType, + function () { + this._snapType = parseInt(this._snapSelect.selectedIndex - 1, 10); + }, + 'Select snapshot resolution' + ); + + var hiddenlink = document.createElement('a'), + button = this._createButton(className + '-button', elem, 'snapshot', + function (event) { + var latlng = map.getCenter(), + bounds = map.getPixelBounds(), + z = map.getZoom(), + zfac; + + if (z > layer.iipMaxZoom) { + zfac = Math.pow(2, z - layer.iipMaxZoom); + z = layer.iipMaxZoom; + } else { + zfac = 1; + } + + var sizex = layer.iipImageSize[z].x * zfac, + sizey = layer.iipImageSize[z].y * zfac, + dx = (bounds.max.x - bounds.min.x), + dy = (bounds.max.y - bounds.min.y); + + hiddenlink.href = layer.getTileUrl({ x: 1, y: 1 } + ).replace(/JTL\=\d+\,\d+/g, + 'RGN=' + bounds.min.x / sizex + ',' + + bounds.min.y / sizey + ',' + + dx / sizex + ',' + dy / sizey + + '&WID=' + (this._snapType === 0 ? + Math.floor(dx / zfac) : + Math.floor(dx / zfac / layer.wcs.scale(z))) + '&CVT=jpeg'); + hiddenlink.download = layer._title + '_' + + L.IIPUtils.latLngToHMSDMS(latlng).replace(/[\s\:\.]/g, '') + + '.jpg'; + hiddenlink.click(); + }, 'Take a snapshot of the displayed image'); + + document.body.appendChild(hiddenlink); + + line = this._addDialogLine('Print:', this._dialog); + elem = this._addDialogElement(line); + button = this._createButton(className + '-button', elem, 'print', + function (event) { + var control = document.querySelector('#map > .leaflet-control-container'); + control.style.display = 'none'; + window.print(); + control.style.display = 'unset'; + }, 'Print current map'); + } + }); L.control.iip.snapshot = function (options) { - return new L.Control.IIP.Snapshot(options); + return new L.Control.IIP.Snapshot(options); }; + + /* # L.Control.Layers.IIP adds new features to the standard L.Control.Layers # @@ -6442,300 +5453,255 @@ L.control.iip.snapshot = function (options) { */ if (typeof require !== 'undefined') { - var $ = require('jquery-browser'); + var $ = require('jquery-browser'); } L.Control.Layers.IIP = L.Control.Layers.extend({ - options: { - title: 'overlay menu', - collapsed: true, - position: 'topright', - autoZIndex: true, - fileMenu: false, - fileURL: '/fcgi-bin/iipsrv.fcgi?FIF=', - fileRoot: '', - fileTreeScript: 'visiomatic/dist/filetree.php', - fileProcessScript: 'visiomatic/dist/processfits.php', - }, - - onAdd: function (map) { - map._layerControl = this; - this._initLayout(); - this._update(); - - // map - // .on('layeradd', this._onLayerChange, this) - // .on('layerremove', this._onLayerChange, this); - - return this._container; - }, - - _initLayout: function () { - var className = 'leaflet-control-layers', - container = (this._container = L.DomUtil.create('div', className)); - - // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released - container.setAttribute('aria-haspopup', true); - - if (!L.Browser.touch) { - L.DomEvent.disableClickPropagation(container).disableScrollPropagation( - container - ); - } else { - L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation); - } - - var form = (this._form = L.DomUtil.create('form', className + '-list')); - - if (this.options.collapsed) { - if (!L.Browser.android) { - L.DomEvent.on( - container, - { - mouseover: this._expand, - mouseout: this._collapse, - }, - this - ); - } - - var link = (this._layersLink = L.DomUtil.create( - 'a', - className + '-toggle', - container - )); - link.href = '#'; - link.title = 'Layers'; - - if (L.Browser.touch) { - L.DomEvent.on(link, 'click', L.DomEvent.stop).on( - link, - 'click', - this._expand, - this - ); - } else { - L.DomEvent.on(link, 'focus', this._expand, this); - } - - // work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033 - L.DomEvent.on( - form, - 'click', - function () { - setTimeout(L.bind(this._onInputClick, this), 0); - }, - this - ); - - this._map.on('click', this._collapse, this); - // TODO keyboard accessibility - } else { - this._expand(); - } - - this._baseLayersList = L.DomUtil.create('div', className + '-base', form); - - if (this.options.fileMenu) { - var addbutton = (this._addButton = L.DomUtil.create( - 'input', - className + '-add', - form - )); - addbutton.type = 'button'; - addbutton.value = 'Add...'; - L.DomEvent.on(addbutton, 'click', this._openFileMenu, this); - } - - this._separator = L.DomUtil.create('div', className + '-separator', form); - this._overlaysList = L.DomUtil.create('div', className + '-overlays', form); - - container.appendChild(form); - }, - - _addItem: function (obj) { - var _this = this, - item = L.DomUtil.create('div', 'leaflet-control-layers-item'), - inputdiv = L.DomUtil.create('div', 'leaflet-control-layers-select', item); - - if (obj.layer.notReady) { - L.DomUtil.create('div', 'leaflet-control-activity', inputdiv); - } else { - var input, - checked = this._map.hasLayer(obj.layer); - if (obj.overlay) { - input = document.createElement('input'); - input.type = 'checkbox'; - input.className = 'leaflet-control-layers-selector'; - input.defaultChecked = checked; - } else { - input = this._createRadioElement('leaflet-base-layers', checked); - } - input.layerId = L.stamp(obj.layer); - L.DomEvent.on(input, 'click', this._onInputClick, this); - inputdiv.appendChild(input); - } - - var name = L.DomUtil.create('div', 'leaflet-control-layers-name', item); - name.innerHTML = ' ' + obj.name; - name.style.textShadow = '0px 0px 5px ' + obj.layer.nameColor; - - var trashbutton = L.DomUtil.create( - 'input', - 'leaflet-control-layers-trash', - item - ); - trashbutton.type = 'button'; - L.DomEvent.on( - trashbutton, - 'click', - function () { - _this.removeLayer(obj.layer); - if (!obj.notReady) { - _this._map.removeLayer(obj.layer); - } - }, - this - ); - - var container = obj.overlay ? this._overlaysList : this._baseLayersList; - container.appendChild(item); - - return item; - }, - - _onInputClick: function () { - var i, - input, - obj, - inputs = this._form.getElementsByTagName('input'), - inputsLen = inputs.length; - - this._handlingClick = true; - - for (i = 0; i < inputsLen; i++) { - input = inputs[i]; - if (!('layerId' in input)) { - continue; - } - obj = this._layers[input.layerId]; - if (input.checked && !this._map.hasLayer(obj.layer)) { - obj.layer.addTo(this._map); - } else if (!input.checked && this._map.hasLayer(obj.layer)) { - this._map.removeLayer(obj.layer); - } - } - - this._handlingClick = false; - }, - - _addDialogLine: function (label, dialog) { - var elem = L.DomUtil.create('div', this._className + '-element', dialog), - text = L.DomUtil.create('span', this._className + '-label', elem); - text.innerHTML = label; - return elem; - }, - - _openFileMenu: function () { - var _this = this, - fileMenu = L.DomUtil.create( - 'div', - 'leaflet-control-filemenu', - this._map._controlContainer - ); - fileMenu.title = 'Open file'; - this._addButton.disabled = true; - L.DomEvent.disableClickPropagation(fileMenu).disableScrollPropagation( - fileMenu - ); - - $('.leaflet-control-filemenu').dialog({ - appendTo: 'body', - close: function (event, ui) { - L.DomUtil.remove(fileMenu); - _this._addButton.disabled = false; - }, - show: { - effect: 'clip', - duration: 250, - }, - hide: { - effect: 'clip', - duration: 250, - }, - height: 200, - }); - var fileTree = L.DomUtil.create( - 'div', - 'leaflet-control-filetree', - fileMenu - ); - fileTree.id = 'leaflet-filetree'; - - $(document).ready(function () { - $('#leaflet-filetree').fileTree( - { - root: _this.options.fileRoot, - script: _this.options.fileTreeScript, - }, - function (fitsname) { - var layercontrol = _this._map._layerControl, - redname = fitsname.replace(/(^.*\/|\..*$)/g, ''), - templayer; - if (layercontrol) { - templayer = new L.LayerGroup(null); - - templayer.notReady = true; - layercontrol.addBaseLayer( - templayer, - 'converting ' + redname + '...' - ); - if (layercontrol.options.collapsed) { - layercontrol._expand(); - } - } - $.post( - _this.options.fileProcessScript, - { - fitsname: fitsname, - }, - function (ptifname) { - ptifname = ptifname.trim(); - var layer = L.tileLayer.iip(_this.options.fileURL + ptifname, { - title: redname, - }); - if (layer.iipMetaReady) { - _this._updateBaseLayer(templayer, layer); - } else { - layer.once('metaload', function () { - _this._updateBaseLayer(templayer, layer); - }); - } - } - ); - } - ); - }); - }, - - _updateBaseLayer: function (templayer, layer) { - var map = this._map, - layercontrol = map._layerControl; - layercontrol.removeLayer(templayer); - map.eachLayer(map.removeLayer); - layer.addTo(map); - layercontrol.addBaseLayer(layer, layer._title); - map.fire('baselayerchange'); - if (layercontrol.options.collapsed) { - layercontrol._collapse(); - } - }, + options: { + title: 'overlay menu', + collapsed: true, + position: 'topright', + autoZIndex: true, + fileMenu: false, + fileURL: '/fcgi-bin/iipsrv.fcgi?FIF=', + fileRoot: '', + fileTreeScript: 'visiomatic/dist/filetree.php', + fileProcessScript: 'visiomatic/dist/processfits.php' + }, + + onAdd: function (map) { + map._layerControl = this; + this._initLayout(); + this._update(); + + // map + // .on('layeradd', this._onLayerChange, this) + // .on('layerremove', this._onLayerChange, this); + + return this._container; + }, + + _initLayout: function () { + var className = 'leaflet-control-layers', + container = this._container = L.DomUtil.create('div', className); + + // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released + container.setAttribute('aria-haspopup', true); + + if (!L.Browser.touch) { + L.DomEvent + .disableClickPropagation(container) + .disableScrollPropagation(container); + } else { + L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation); + } + + var form = this._form = L.DomUtil.create('form', className + '-list'); + + if (this.options.collapsed) { + if (!L.Browser.android) { + L.DomEvent.on(container, { + mouseover: this._expand, + mouseout: this._collapse + }, this); + } + + var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container); + link.href = '#'; + link.title = 'Layers'; + + if (L.Browser.touch) { + L.DomEvent + .on(link, 'click', L.DomEvent.stop) + .on(link, 'click', this._expand, this); + } else { + L.DomEvent.on(link, 'focus', this._expand, this); + } + + // work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033 + L.DomEvent.on(form, 'click', function () { + setTimeout(L.bind(this._onInputClick, this), 0); + }, this); + + this._map.on('click', this._collapse, this); + // TODO keyboard accessibility + } else { + this._expand(); + } + + this._baseLayersList = L.DomUtil.create('div', className + '-base', form); + + if (this.options.fileMenu) { + var addbutton = this._addButton = L.DomUtil.create('input', className + '-add', form); + addbutton.type = 'button'; + addbutton.value = 'Add...'; + L.DomEvent.on(addbutton, 'click', this._openFileMenu, this); + } + + this._separator = L.DomUtil.create('div', className + '-separator', form); + this._overlaysList = L.DomUtil.create('div', className + '-overlays', form); + + container.appendChild(form); + }, + + _addItem: function (obj) { + var _this = this, + item = L.DomUtil.create('div', 'leaflet-control-layers-item'), + inputdiv = L.DomUtil.create('div', 'leaflet-control-layers-select', item); + + if (obj.layer.notReady) { + L.DomUtil.create('div', 'leaflet-control-activity', inputdiv); + } else { + var input, + checked = this._map.hasLayer(obj.layer); + if (obj.overlay) { + input = document.createElement('input'); + input.type = 'checkbox'; + input.className = 'leaflet-control-layers-selector'; + input.defaultChecked = checked; + } + else { + input = this._createRadioElement('leaflet-base-layers', checked); + } + input.layerId = L.stamp(obj.layer); + L.DomEvent.on(input, 'click', this._onInputClick, this); + inputdiv.appendChild(input); + } + + var name = L.DomUtil.create('div', 'leaflet-control-layers-name', item); + name.innerHTML = ' ' + obj.name; + name.style.textShadow = '0px 0px 5px ' + obj.layer.nameColor; + + var trashbutton = L.DomUtil.create('input', 'leaflet-control-layers-trash', item); + trashbutton.type = 'button'; + L.DomEvent.on(trashbutton, 'click', function () { + _this.removeLayer(obj.layer); + if (!obj.notReady) { + _this._map.removeLayer(obj.layer); + } + }, this); + + var container = obj.overlay ? this._overlaysList : this._baseLayersList; + container.appendChild(item); + + return item; + }, + + _onInputClick: function () { + var i, input, obj, + inputs = this._form.getElementsByTagName('input'), + inputsLen = inputs.length; + + this._handlingClick = true; + + for (i = 0; i < inputsLen; i++) { + input = inputs[i]; + if (!('layerId' in input)) { + continue; + } + obj = this._layers[input.layerId]; + if (input.checked && !this._map.hasLayer(obj.layer)) { + obj.layer.addTo(this._map); + } else if (!input.checked && this._map.hasLayer(obj.layer)) { + this._map.removeLayer(obj.layer); + } + } + + this._handlingClick = false; + }, + + _addDialogLine: function (label, dialog) { + var elem = L.DomUtil.create('div', this._className + '-element', dialog), + text = L.DomUtil.create('span', this._className + '-label', elem); + text.innerHTML = label; + return elem; + }, + + _openFileMenu: function () { + var _this = this, + fileMenu = L.DomUtil.create('div', 'leaflet-control-filemenu', + this._map._controlContainer); + fileMenu.title = 'Open file'; + this._addButton.disabled = true; + L.DomEvent + .disableClickPropagation(fileMenu) + .disableScrollPropagation(fileMenu); + + $('.leaflet-control-filemenu').dialog({ + appendTo: 'body', + close: function (event, ui) { + L.DomUtil.remove(fileMenu); + _this._addButton.disabled = false; + }, + show: { + effect: 'clip', + duration: 250 + }, + hide: { + effect: 'clip', + duration: 250 + }, + height: 200 + }); + var fileTree = L.DomUtil.create('div', 'leaflet-control-filetree', + fileMenu); + fileTree.id = 'leaflet-filetree'; + + $(document).ready(function () { + $('#leaflet-filetree').fileTree({ + root: _this.options.fileRoot, + script: _this.options.fileTreeScript + }, + function (fitsname) { + var layercontrol = _this._map._layerControl, + redname = fitsname.replace(/(^.*\/|\..*$)/g, ''), + templayer; + if (layercontrol) { + templayer = new L.LayerGroup(null); + + templayer.notReady = true; + layercontrol.addBaseLayer(templayer, 'converting ' + redname + '...'); + if (layercontrol.options.collapsed) { + layercontrol._expand(); + } + } + $.post(_this.options.fileProcessScript, { + fitsname: fitsname + }, function (ptifname) { + ptifname = ptifname.trim(); + var layer = L.tileLayer.iip(_this.options.fileURL + ptifname, { title: redname }); + if (layer.iipMetaReady) { + _this._updateBaseLayer(templayer, layer); + } else { + layer.once('metaload', function () { + _this._updateBaseLayer(templayer, layer); + }); + } + }); + }); + }); + }, + + _updateBaseLayer: function (templayer, layer) { + var map = this._map, + layercontrol = map._layerControl; + layercontrol.removeLayer(templayer); + map.eachLayer(map.removeLayer); + layer.addTo(map); + layercontrol.addBaseLayer(layer, layer._title); + map.fire('baselayerchange'); + if (layercontrol.options.collapsed) { + layercontrol._collapse(); + } + } }); L.control.layers.iip = function (baselayers, overlays, options) { - return new L.Control.Layers.IIP(baselayers, overlays, options); + return new L.Control.Layers.IIP(baselayers, overlays, options); }; + + /* # L.Control.Reticle adds a reticle at the center of the map container # @@ -6747,43 +5713,38 @@ L.control.layers.iip = function (baselayers, overlays, options) { # Last modified: 10/02/2014 */ L.Control.Reticle = L.Control.extend({ - options: { - position: 'bottomleft', - }, - - onAdd: function (map) { - // Create central reticle - var reticle = (this._reticle = L.DomUtil.create( - 'div', - 'leaflet-reticle', - this._map._controlContainer - )), - style = reticle.style; - style.position = 'absolute'; - style.left = '50%'; - style.bottom = '50%'; - style.textAlign = 'center'; - style.verticalAlign = 'middle'; - style.pointerEvents = 'none'; - reticle.innerHTML = ''; - - var container = (this._container = L.DomUtil.create( - 'div', - 'leaflet-dummy' - )); - - return container; - }, - - onRemove: function (map) { - this._reticle.parentNode.removeChild(this._reticle); - }, + options: { + position: 'bottomleft' + }, + + onAdd: function (map) { + // Create central reticle + var reticle = this._reticle = L.DomUtil.create('div', 'leaflet-reticle', this._map._controlContainer), + style = reticle.style; + style.position = 'absolute'; + style.left = '50%'; + style.bottom = '50%'; + style.textAlign = 'center'; + style.verticalAlign = 'middle'; + style.pointerEvents = 'none'; + reticle.innerHTML = ''; + + var container = this._container = L.DomUtil.create('div', 'leaflet-dummy'); + + return container; + }, + + onRemove: function (map) { + this._reticle.parentNode.removeChild(this._reticle); + } + }); L.control.reticle = function (options) { - return new L.Control.Reticle(options); + return new L.Control.Reticle(options); }; + /* # L.Control.Scale.WCS adds degree and pixel units to the standard L.Control.Scale # @@ -6796,174 +5757,159 @@ L.control.reticle = function (options) { */ L.Control.Scale.WCS = L.Control.Scale.extend({ - options: { - position: 'bottomleft', - title: 'Scale', - maxWidth: 128, - metric: false, - imperial: false, - degrees: true, - pixels: true, - custom: false, - customScale: 1.0, - customUnits: '', - planetRadius: 6378137.0, - updateWhenIdle: false, - }, - - _addScales: function (options, className, container) { - if (options.metric) { - this._mScale = L.DomUtil.create('div', className, container); - this._mScale.title = options.metricTitle - ? options.metricTitle - : options.title; - } - if (options.imperial) { - this._iScale = L.DomUtil.create('div', className, container); - this._iScale.title = options.imperialTitle - ? options.imperialTitle - : options.title; - } - if (options.degrees) { - this._dScale = L.DomUtil.create('div', className, container); - this._dScale.title = options.degreesTitle - ? options.degreesTitle - : options.title; - } - if (options.pixels) { - this._pScale = L.DomUtil.create('div', className, container); - this._pScale.title = options.pixelsTitle - ? options.pixelsTitle - : options.title; - } - if (options.custom) { - this._cScale = L.DomUtil.create('div', className, container); - this._cScale.title = options.customTitle - ? options.customTitle - : options.title; - } - - this.angular = options.metric || options.imperial || options.degrees; - }, - - _update: function () { - var options = this.options, - map = this._map, - crs = map.options.crs; - - if (options.pixels && crs.options && crs.options.nzoom) { - var pixelScale = Math.pow(2.0, crs.options.nzoom - 1 - map.getZoom()); - this._updatePixels(pixelScale * options.maxWidth); - } - - if (options.custom && crs.options && crs.options.nzoom) { - var customScale = - Math.pow(2.0, crs.options.nzoom - 1 - map.getZoom()) * - options.customScale; - this._updateCustom(customScale * options.maxWidth, options.customUnits); - } - - if (this.angular) { - var center = map.getCenter(), - cosLat = Math.cos((center.lat * Math.PI) / 180), - dist = Math.sqrt(this._jacobian(center)) * cosLat, - maxDegrees = dist * options.maxWidth; - - if (options.metric) { - this._updateMetric( - ((maxDegrees * Math.PI) / 180.0) * options.planetRadius - ); - } - if (options.imperial) { - this._updateImperial( - ((maxDegrees * Math.PI) / 180.0) * options.planetRadius - ); - } - if (options.degrees) { - this._updateDegrees(maxDegrees); - } - } - }, - - // Return the Jacobian determinant of the astrometric transformation at latLng - _jacobian: function (latlng) { - var map = this._map, - p0 = map.project(latlng), - latlngdx = map.unproject(p0.add([10.0, 0.0])), - latlngdy = map.unproject(p0.add([0.0, 10.0])); - return ( - 0.01 * - Math.abs( - (latlngdx.lng - latlng.lng) * (latlngdy.lat - latlng.lat) - - (latlngdy.lng - latlng.lng) * (latlngdx.lat - latlng.lat) - ) - ); - }, - - _updateCustom: function (maxCust, units) { - var scale = this._cScale; - - if (maxCust > 1.0e9) { - var maxGCust = maxCust * 1.0e-9, - gCust = this._getRoundNum(maxGCust); - this._updateScale(scale, gCust + ' G' + units, gCust / maxGCust); - } else if (maxCust > 1.0e6) { - var maxMCust = maxCust * 1.0e-6, - mCust = this._getRoundNum(maxMCust); - this._updateScale(scale, mCust + ' M' + units, mCust / maxMCust); - } else if (maxCust > 1.0e3) { - var maxKCust = maxCust * 1.0e-3, - kCust = this._getRoundNum(maxKCust); - this._updateScale(scale, kCust + ' k' + units, kCust / maxKCust); - } else { - var cust = this._getRoundNum(maxCust); - this._updateScale(scale, cust + ' ' + units, cust / maxCust); - } - }, - - _updatePixels: function (maxPix) { - var scale = this._pScale; - - if (maxPix > 1.0e6) { - var maxMPix = maxPix * 1.0e-6, - mPix = this._getRoundNum(maxMPix); - this._updateScale(scale, mPix + ' Mpx', mPix / maxMPix); - } else if (maxPix > 1.0e3) { - var maxKPix = maxPix * 1.0e-3, - kPix = this._getRoundNum(maxKPix); - this._updateScale(scale, kPix + ' kpx', kPix / maxKPix); - } else { - var pix = this._getRoundNum(maxPix); - this._updateScale(scale, pix + ' px', pix / maxPix); - } - }, - - _updateDegrees: function (maxDegrees) { - var maxSeconds = maxDegrees * 3600.0, - scale = this._dScale; - - if (maxSeconds < 1.0) { - var maxMas = maxSeconds * 1000.0, - mas = this._getRoundNum(maxMas); - this._updateScale(scale, mas + ' mas', mas / maxMas); - } else if (maxSeconds < 60.0) { - var seconds = this._getRoundNum(maxSeconds); - this._updateScale(scale, seconds + ' "', seconds / maxSeconds); - } else if (maxSeconds < 3600.0) { - var maxMinutes = maxDegrees * 60.0, - minutes = this._getRoundNum(maxMinutes); - this._updateScale(scale, minutes + ' '', minutes / maxMinutes); - } else { - var degrees = this._getRoundNum(maxDegrees); - this._updateScale(scale, degrees + ' °', degrees / maxDegrees); - } - }, + options: { + position: 'bottomleft', + title: 'Scale', + maxWidth: 128, + metric: false, + imperial: false, + degrees: true, + pixels: true, + custom: false, + customScale: 1.0, + customUnits: '', + planetRadius: 6378137.0, + updateWhenIdle: false + }, + + _addScales: function (options, className, container) { + if (options.metric) { + this._mScale = L.DomUtil.create('div', className, container); + this._mScale.title = options.metricTitle ? options.metricTitle : options.title; + } + if (options.imperial) { + this._iScale = L.DomUtil.create('div', className, container); + this._iScale.title = options.imperialTitle ? options.imperialTitle : options.title; + } + if (options.degrees) { + this._dScale = L.DomUtil.create('div', className, container); + this._dScale.title = options.degreesTitle ? options.degreesTitle : options.title; + } + if (options.pixels) { + this._pScale = L.DomUtil.create('div', className, container); + this._pScale.title = options.pixelsTitle ? options.pixelsTitle : options.title; + } + if (options.custom) { + this._cScale = L.DomUtil.create('div', className, container); + this._cScale.title = options.customTitle ? options.customTitle : options.title; + } + + this.angular = options.metric || options.imperial || options.degrees; + }, + + _update: function () { + var options = this.options, + map = this._map, + crs = map.options.crs; + + if (options.pixels && crs.options && crs.options.nzoom) { + var pixelScale = Math.pow(2.0, crs.options.nzoom - 1 - map.getZoom()); + this._updatePixels(pixelScale * options.maxWidth); + } + + if (options.custom && crs.options && crs.options.nzoom) { + var customScale = Math.pow(2.0, + crs.options.nzoom - 1 - map.getZoom()) * options.customScale; + this._updateCustom(customScale * options.maxWidth, options.customUnits); + } + + if (this.angular) { + var center = map.getCenter(), + cosLat = Math.cos(center.lat * Math.PI / 180), + dist = Math.sqrt(this._jacobian(center)) * cosLat, + maxDegrees = dist * options.maxWidth; + + if (options.metric) { + this._updateMetric(maxDegrees * Math.PI / 180.0 * options.planetRadius); + } + if (options.imperial) { + this._updateImperial(maxDegrees * Math.PI / 180.0 * options.planetRadius); + } + if (options.degrees) { + this._updateDegrees(maxDegrees); + } + } + }, + + // Return the Jacobian determinant of the astrometric transformation at latLng + _jacobian: function (latlng) { + var map = this._map, + p0 = map.project(latlng), + latlngdx = map.unproject(p0.add([10.0, 0.0])), + latlngdy = map.unproject(p0.add([0.0, 10.0])); + return 0.01 * Math.abs((latlngdx.lng - latlng.lng) * + (latlngdy.lat - latlng.lat) - + (latlngdy.lng - latlng.lng) * + (latlngdx.lat - latlng.lat)); + }, + + _updateCustom: function (maxCust, units) { + var scale = this._cScale; + + if (maxCust > 1.0e9) { + var maxGCust = maxCust * 1.0e-9, + gCust = this._getRoundNum(maxGCust); + this._updateScale(scale, gCust + ' G' + units, gCust / maxGCust); + } else if (maxCust > 1.0e6) { + var maxMCust = maxCust * 1.0e-6, + mCust = this._getRoundNum(maxMCust); + this._updateScale(scale, mCust + ' M' + units, mCust / maxMCust); + } else if (maxCust > 1.0e3) { + var maxKCust = maxCust * 1.0e-3, + kCust = this._getRoundNum(maxKCust); + this._updateScale(scale, kCust + ' k' + units, kCust / maxKCust); + } else { + var cust = this._getRoundNum(maxCust); + this._updateScale(scale, cust + ' ' + units, cust / maxCust); + } + }, + + _updatePixels: function (maxPix) { + var scale = this._pScale; + + if (maxPix > 1.0e6) { + var maxMPix = maxPix * 1.0e-6, + mPix = this._getRoundNum(maxMPix); + this._updateScale(scale, mPix + ' Mpx', mPix / maxMPix); + } else if (maxPix > 1.0e3) { + var maxKPix = maxPix * 1.0e-3, + kPix = this._getRoundNum(maxKPix); + this._updateScale(scale, kPix + ' kpx', kPix / maxKPix); + } else { + var pix = this._getRoundNum(maxPix); + this._updateScale(scale, pix + ' px', pix / maxPix); + } + }, + + _updateDegrees: function (maxDegrees) { + var maxSeconds = maxDegrees * 3600.0, + scale = this._dScale; + + if (maxSeconds < 1.0) { + var maxMas = maxSeconds * 1000.0, + mas = this._getRoundNum(maxMas); + this._updateScale(scale, mas + ' mas', mas / maxMas); + } else if (maxSeconds < 60.0) { + var seconds = this._getRoundNum(maxSeconds); + this._updateScale(scale, seconds + ' "', seconds / maxSeconds); + } else if (maxSeconds < 3600.0) { + var maxMinutes = maxDegrees * 60.0, + minutes = this._getRoundNum(maxMinutes); + this._updateScale(scale, minutes + ' '', minutes / maxMinutes); + } else { + var degrees = this._getRoundNum(maxDegrees); + this._updateScale(scale, degrees + ' °', degrees / maxDegrees); + } + } + }); L.control.scale.wcs = function (options) { - return new L.Control.Scale.WCS(options); + return new L.Control.Scale.WCS(options); }; + + /* # L.Control.Sidebar adds support for responsive side bars # Adapted from the leaflet-sidebar plugin by Tobias Bieniek @@ -6997,269 +5943,268 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ L.Control.Sidebar = L.Control.extend({ - includes: L.Mixin.Events, - - options: { - position: 'left', - title: 'Toggle advanced menu', - collapsed: true, - forceSeparateButton: false, - }, - - /** - * Create a new sidebar on this jQuery object. - * - * @constructor - * @param {string} id - The id of the sidebar element (without the # character) - * @param {Object} [options] - Optional options object - * @param {string} [options.position=left] - Position of the sidebar: 'left' or 'right' - */ - initialize: function (options) { - var i, child; - - L.setOptions(this, options); - - // Create sidebar - this._sidebar = L.DomUtil.create('div', 'leaflet-container sidebar'); - if (this.options.collapsed) { - L.DomUtil.addClass(this._sidebar, 'collapsed'); - } else { - L.DomUtil.addClass(this._sidebar, 'closed'); - } - // Attach .sidebar-left/right class - L.DomUtil.addClass(this._sidebar, 'sidebar-' + this.options.position); - - // Attach touch styling if necessary - if (L.Browser.touch) { - L.DomUtil.addClass(this._sidebar, 'leaflet-touch'); - } - - // Create containers for tabs and their contents (panes) - this._tabs = L.DomUtil.create('div', 'sidebar-tabs', this._sidebar); - this._tabitems = []; - - this._container = L.DomUtil.create('div', 'sidebar-content', this._sidebar); - this._panes = []; - this._closeButtons = []; - }, - - /** - * Add this sidebar to the specified map. - * - * @param {L.Map} map - * @returns {L.Control.Sidebar} - */ - addTo: function (map) { - var className = 'leaflet-control-zoom-sidebar', - parent = map._controlContainer, - buttonContainer; - - // Create sidebar - L.DomUtil.addClass(map._container, 'sidebar-map'); - parent.insertBefore(this._sidebar, parent.firstChild); - L.DomEvent.disableClickPropagation(this._sidebar).disableScrollPropagation( - this._sidebar - ); - - this._map = map; - - // Create sidebar toggle button - if (map.zoomControl && !this.options.forceSeparateButton) { - buttonContainer = map.zoomControl._container; - } else { - buttonContainer = L.DomUtil.create('div', 'leaflet-bar'); - } - - this._toggleButton = this._createButton( - this.options.title, - className + (this.options.collapsed ? ' collapsed' : ''), - buttonContainer - ); - - return this; - }, - - // Add sidebar tab list - addTabList: function () { - this._tablist = L.DomUtil.create('ul', '', this._tabs); - this._tablist.setAttribute('role', 'tablist'); - return this._tablist; - }, - - // Add sidebar tab - addTab: function (id, tabClass, title, content, sideClass) { - var tablist = this._tablist ? this._tablist : this.addTabList(), - item = L.DomUtil.create('li', '', tablist), - button = L.DomUtil.create('a', tabClass, item); - item.setAttribute('role', 'tab'); - item._sidebar = this; - button.href = '#' + id; - button.id = id + '-toggle'; - button.title = title; - L.DomEvent.on(button, 'click', L.DomEvent.preventDefault); - L.DomEvent.on(button, 'click', this._onClick, item); - item.sideClass = sideClass; - this._tabitems.push(item); - - // Sidebar pane - var pane = L.DomUtil.create('div', 'sidebar-pane', this._container), - header = L.DomUtil.create('h1', 'sidebar-header', pane); - - header.innerHTML = title; - - var closeButton = L.DomUtil.create('div', 'sidebar-close', header); - this._closeButtons.push(closeButton); - L.DomEvent.on(closeButton, 'click', this._onCloseClick, this); - pane.id = id; - pane.sideClass = sideClass; - pane.appendChild(content); - this._panes.push(pane); - return pane; - }, - - /** - * Remove this sidebar from the map. - * - * @param {L.Map} map - * @returns {L.Control.Sidebar} - */ - removeFrom: function (map) { - var i, child; - - this._map = null; - - for (i = this._tabitems.length - 1; i >= 0; i--) { - child = this._tabitems[i]; - L.DomEvent.off(child.querySelector('a'), 'click', this._onClick); - } - - for (i = this._closeButtons.length - 1; i >= 0; i--) { - child = this._closeButtons[i]; - L.DomEvent.off(child, 'click', this._onCloseClick, this); - } - - return this; - }, - - /** - * Open sidebar (if necessary) and show the specified tab. - * - * @param {string} id - The id of the tab to show (without the # character) - */ - open: function (id) { - var i, child; - - // hide old active contents and show new content - for (i = this._panes.length - 1; i >= 0; i--) { - child = this._panes[i]; - if (child.id === id) { - L.DomUtil.addClass(child, 'active'); - if (child.sideClass) { - L.DomUtil.addClass(this._sidebar, child.sideClass); - } - } else if (L.DomUtil.hasClass(child, 'active')) { - L.DomUtil.removeClass(child, 'active'); - if (child.sideClass) { - L.DomUtil.removeClass(this._sidebar, child.sideClass); - } - } - } - - // remove old active highlights and set new highlight - for (i = this._tabitems.length - 1; i >= 0; i--) { - child = this._tabitems[i]; - if (child.querySelector('a').hash === '#' + id) { - L.DomUtil.addClass(child, 'active'); - } else if (L.DomUtil.hasClass(child, 'active')) { - L.DomUtil.removeClass(child, 'active'); - } - } - - this.fire('content', { id: id }); - - // open sidebar (if necessary) - if (L.DomUtil.hasClass(this._sidebar, 'closed')) { - this.fire('opening'); - L.DomUtil.removeClass(this._sidebar, 'closed'); - } - - return this; - }, - - /** - * Close the sidebar (if necessary). - */ - close: function () { - // remove old active highlights - for (var i = this._tabitems.length - 1; i >= 0; i--) { - var child = this._tabitems[i]; - if (L.DomUtil.hasClass(child, 'active')) { - L.DomUtil.removeClass(child, 'active'); - if (child.sideClass) { - L.DomUtil.removeClass(this._sidebar, child.sideClass); - } - } - } - - // close sidebar - if (!L.DomUtil.hasClass(this._sidebar, 'closed')) { - this.fire('closing'); - L.DomUtil.addClass(this._sidebar, 'closed'); - } - - return this; - }, - - /** - * Collapse/Expanding the sidebar. - */ - toggle: function () { - this.close(); - if (L.DomUtil.hasClass(this._sidebar, 'collapsed')) { - L.DomUtil.addClass(this._sidebar, 'closed'); - this.fire('expanding'); - L.DomUtil.removeClass(this._sidebar, 'collapsed'); - L.DomUtil.removeClass(this._toggleButton, 'collapsed'); - } else { - L.DomUtil.removeClass(this._sidebar, 'closed'); - this.fire('collapsing'); - L.DomUtil.addClass(this._sidebar, 'collapsed'); - L.DomUtil.addClass(this._toggleButton, 'collapsed'); - } - }, - - /** - * @private - */ - _onClick: function () { - if (L.DomUtil.hasClass(this, 'active')) { - this._sidebar.close(); - } else if (!L.DomUtil.hasClass(this, 'disabled')) { - this._sidebar.open(this.querySelector('a').hash.slice(1)); - } - }, - - /** - * @private - */ - _onCloseClick: function () { - this.close(); - }, - - /** - * @private - */ - _createButton: function (title, className, container) { - var link = L.DomUtil.create('a', className, container); - link.href = '#'; - link.title = title; - - L.DomEvent.addListener(link, 'click', L.DomEvent.stopPropagation) - .addListener(link, 'click', L.DomEvent.preventDefault) - .addListener(link, 'click', this.toggle, this); - - return link; - }, + includes: L.Mixin.Events, + + options: { + position: 'left', + title: 'Toggle advanced menu', + collapsed: true, + forceSeparateButton: false + }, + + /** + * Create a new sidebar on this jQuery object. + * + * @constructor + * @param {string} id - The id of the sidebar element (without the # character) + * @param {Object} [options] - Optional options object + * @param {string} [options.position=left] - Position of the sidebar: 'left' or 'right' + */ + initialize: function (options) { + var i, child; + + L.setOptions(this, options); + + // Create sidebar + this._sidebar = L.DomUtil.create('div', 'leaflet-container sidebar'); + if (this.options.collapsed) { + L.DomUtil.addClass(this._sidebar, 'collapsed'); + } else { + L.DomUtil.addClass(this._sidebar, 'closed'); + } + // Attach .sidebar-left/right class + L.DomUtil.addClass(this._sidebar, 'sidebar-' + this.options.position); + + // Attach touch styling if necessary + if (L.Browser.touch) { + L.DomUtil.addClass(this._sidebar, 'leaflet-touch'); + } + + // Create containers for tabs and their contents (panes) + this._tabs = L.DomUtil.create('div', 'sidebar-tabs', this._sidebar); + this._tabitems = []; + + this._container = L.DomUtil.create('div', 'sidebar-content', this._sidebar); + this._panes = []; + this._closeButtons = []; + }, + + /** + * Add this sidebar to the specified map. + * + * @param {L.Map} map + * @returns {L.Control.Sidebar} + */ + addTo: function (map) { + var className = 'leaflet-control-zoom-sidebar', + parent = map._controlContainer, + buttonContainer; + + // Create sidebar + L.DomUtil.addClass(map._container, 'sidebar-map'); + parent.insertBefore(this._sidebar, parent.firstChild); + L.DomEvent + .disableClickPropagation(this._sidebar) + .disableScrollPropagation(this._sidebar); + + this._map = map; + + // Create sidebar toggle button + if (map.zoomControl && !this.options.forceSeparateButton) { + buttonContainer = map.zoomControl._container; + } else { + buttonContainer = L.DomUtil.create('div', 'leaflet-bar'); + } + + this._toggleButton = this._createButton(this.options.title, + className + (this.options.collapsed ? ' collapsed' : ''), buttonContainer); + + return this; + }, + + // Add sidebar tab list + addTabList: function () { + this._tablist = L.DomUtil.create('ul', '', this._tabs); + this._tablist.setAttribute('role', 'tablist'); + return this._tablist; + }, + + // Add sidebar tab + addTab: function (id, tabClass, title, content, sideClass) { + var tablist = this._tablist ? this._tablist : this.addTabList(), + item = L.DomUtil.create('li', '', tablist), + button = L.DomUtil.create('a', tabClass, item); + item.setAttribute('role', 'tab'); + item._sidebar = this; + button.href = '#' + id; + button.id = id + '-toggle'; + button.title = title; + L.DomEvent.on(button, 'click', L.DomEvent.preventDefault); + L.DomEvent.on(button, 'click', this._onClick, item); + item.sideClass = sideClass; + this._tabitems.push(item); + + // Sidebar pane + var pane = L.DomUtil.create('div', 'sidebar-pane', this._container), + header = L.DomUtil.create('h1', 'sidebar-header', pane); + + header.innerHTML = title; + + var closeButton = L.DomUtil.create('div', 'sidebar-close', header); + this._closeButtons.push(closeButton); + L.DomEvent.on(closeButton, 'click', this._onCloseClick, this); + pane.id = id; + pane.sideClass = sideClass; + pane.appendChild(content); + this._panes.push(pane); + return pane; + }, + + /** + * Remove this sidebar from the map. + * + * @param {L.Map} map + * @returns {L.Control.Sidebar} + */ + removeFrom: function (map) { + var i, child; + + this._map = null; + + for (i = this._tabitems.length - 1; i >= 0; i--) { + child = this._tabitems[i]; + L.DomEvent.off(child.querySelector('a'), 'click', this._onClick); + } + + for (i = this._closeButtons.length - 1; i >= 0; i--) { + child = this._closeButtons[i]; + L.DomEvent.off(child, 'click', this._onCloseClick, this); + } + + return this; + }, + + /** + * Open sidebar (if necessary) and show the specified tab. + * + * @param {string} id - The id of the tab to show (without the # character) + */ + open: function (id) { + var i, child; + + // hide old active contents and show new content + for (i = this._panes.length - 1; i >= 0; i--) { + child = this._panes[i]; + if (child.id === id) { + L.DomUtil.addClass(child, 'active'); + if (child.sideClass) { + L.DomUtil.addClass(this._sidebar, child.sideClass); + } + } else if (L.DomUtil.hasClass(child, 'active')) { + L.DomUtil.removeClass(child, 'active'); + if (child.sideClass) { + L.DomUtil.removeClass(this._sidebar, child.sideClass); + } + } + } + + // remove old active highlights and set new highlight + for (i = this._tabitems.length - 1; i >= 0; i--) { + child = this._tabitems[i]; + if (child.querySelector('a').hash === '#' + id) { + L.DomUtil.addClass(child, 'active'); + } else if (L.DomUtil.hasClass(child, 'active')) { + L.DomUtil.removeClass(child, 'active'); + } + } + + this.fire('content', { id: id }); + + // open sidebar (if necessary) + if (L.DomUtil.hasClass(this._sidebar, 'closed')) { + this.fire('opening'); + L.DomUtil.removeClass(this._sidebar, 'closed'); + } + + return this; + }, + + /** + * Close the sidebar (if necessary). + */ + close: function () { + // remove old active highlights + for (var i = this._tabitems.length - 1; i >= 0; i--) { + var child = this._tabitems[i]; + if (L.DomUtil.hasClass(child, 'active')) { + L.DomUtil.removeClass(child, 'active'); + if (child.sideClass) { + L.DomUtil.removeClass(this._sidebar, child.sideClass); + } + } + } + + // close sidebar + if (!L.DomUtil.hasClass(this._sidebar, 'closed')) { + this.fire('closing'); + L.DomUtil.addClass(this._sidebar, 'closed'); + } + + return this; + }, + + /** + * Collapse/Expanding the sidebar. + */ + toggle: function () { + this.close(); + if (L.DomUtil.hasClass(this._sidebar, 'collapsed')) { + L.DomUtil.addClass(this._sidebar, 'closed'); + this.fire('expanding'); + L.DomUtil.removeClass(this._sidebar, 'collapsed'); + L.DomUtil.removeClass(this._toggleButton, 'collapsed'); + } else { + L.DomUtil.removeClass(this._sidebar, 'closed'); + this.fire('collapsing'); + L.DomUtil.addClass(this._sidebar, 'collapsed'); + L.DomUtil.addClass(this._toggleButton, 'collapsed'); + } + }, + + /** + * @private + */ + _onClick: function () { + if (L.DomUtil.hasClass(this, 'active')) { + this._sidebar.close(); + } else if (!L.DomUtil.hasClass(this, 'disabled')) { + this._sidebar.open(this.querySelector('a').hash.slice(1)); + } + }, + + /** + * @private + */ + _onCloseClick: function () { + this.close(); + }, + + /** + * @private + */ + _createButton: function (title, className, container) { + var link = L.DomUtil.create('a', className, container); + link.href = '#'; + link.title = title; + + L.DomEvent + .addListener(link, 'click', L.DomEvent.stopPropagation) + .addListener(link, 'click', L.DomEvent.preventDefault) + .addListener(link, 'click', this.toggle, this); + + return link; + } + }); /** @@ -7274,9 +6219,10 @@ L.Control.Sidebar = L.Control.extend({ * @returns {L.Control.Sidebar} A new sidebar instance */ L.control.sidebar = function (map, options) { - return new L.Control.Sidebar(map, options); + return new L.Control.Sidebar(map, options); }; + /* # L.Control.WCS Manage coordinate display and input # @@ -7288,218 +6234,174 @@ L.control.sidebar = function (map, options) { # Last modified: 30/11/2017 */ L.Control.WCS = L.Control.extend({ - options: { - position: 'bottomleft', - title: 'Center coordinates. Click to change', - coordinates: [ - { - label: 'RA, Dec', - units: 'HMS', - nativeCelsys: false, - }, - ], - centerQueryKey: 'center', - fovQueryKey: 'fov', - sesameURL: 'https://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame', - }, - - onAdd: function (map) { - // Create coordinate input/display box - var _this = this, - className = 'leaflet-control-wcs', - dialog = (this._wcsdialog = L.DomUtil.create( - 'div', - className + '-dialog' - )), - coordSelect = L.DomUtil.create('select', className + '-select', dialog), - choose = document.createElement('option'), - coords = this.options.coordinates, - opt = [], - coordIndex; - - L.DomEvent.disableClickPropagation(coordSelect); - this._currentCoord = 0; - coordSelect.id = 'leaflet-coord-select'; - coordSelect.title = 'Switch coordinate system'; - for (var c in coords) { - opt[c] = document.createElement('option'); - opt[c].text = coords[c].label; - coordIndex = parseInt(c, 10); - opt[c].value = coordIndex; - if (coordIndex === 0) { - opt[c].selected = true; - } - coordSelect.add(opt[c], null); - } - - L.DomEvent.on(coordSelect, 'change', function (e) { - _this._currentCoord = coordSelect.value; - _this._onDrag(); - }); - - var input = (this._wcsinput = L.DomUtil.create( - 'input', - className + '-input', - dialog - )); - - L.DomEvent.disableClickPropagation(input); - input.type = 'text'; - input.title = this.options.title; - - // Speech recognition on WebKit engine - if ('webkitSpeechRecognition' in window) { - input.setAttribute('x-webkit-speech', 'x-webkit-speech'); - } - - map.on('move zoomend', this._onDrag, this); - L.DomEvent.on( - input, - 'focus', - function () { - this.setSelectionRange(0, this.value.length); - }, - input - ); - L.DomEvent.on( - input, - 'change', - function () { - this.panTo(this._wcsinput.value); - }, - this - ); - - var clipboardbutton = L.DomUtil.create( - 'div', - className + '-clipboard', - dialog - ); - clipboardbutton.title = 'Copy to clipboard'; - L.DomEvent.on( - clipboardbutton, - 'click', - function () { - var stateObj = {}, - url = location.href, - wcs = this._map.options.crs, - latlng = map.getCenter(); - L.IIPUtils.flashElement(this._wcsinput); - url = L.IIPUtils.updateURL( - url, - this.options.centerQueryKey, - L.IIPUtils.latLngToHMSDMS(latlng) - ); - url = L.IIPUtils.updateURL( - url, - this.options.fovQueryKey, - wcs.zoomToFov(map, map.getZoom(), latlng).toPrecision(4) - ); - history.pushState(stateObj, '', url); - L.IIPUtils.copyToClipboard(url); - }, - this - ); - - return this._wcsdialog; - }, - - onRemove: function (map) { - map.off('drag', this._onDrag); - }, - - _onDrag: function (e) { - var latlng = this._map.getCenter(), - wcs = this._map.options.crs, - coord = this.options.coordinates[this._currentCoord]; - - if (wcs.pixelFlag) { - this._wcsinput.value = - latlng.lng.toFixed(0) + ' , ' + latlng.lat.toFixed(0); - } else { - if (!coord.nativeCelsys && wcs.forceNativeCelsys) { - latlng = wcs.celsysToEq(latlng); - } else if (coord.nativeCelsys && wcs.forceNativeCelsys === false) { - latlng = wcs.eqToCelsys(latlng); - } - switch (coord.units) { - case 'HMS': - this._wcsinput.value = L.IIPUtils.latLngToHMSDMS(latlng); - break; - case 'deg': - this._wcsinput.value = - latlng.lng.toFixed(5) + ' , ' + latlng.lat.toFixed(5); - break; - default: - this._wcsinput.value = - latlng.lng.toFixed(1) + ' , ' + latlng.lat.toFixed(1); - break; - } - } - }, - - panTo: function (str) { - var wcs = this._map.options.crs, - coord = this.options.coordinates[this._currentCoord], - latlng = wcs.parseCoords(str); - - if (latlng) { - if (wcs.pixelFlag) { - this._map.panTo(latlng); - } else { - if (!coord.nativeCelsys && wcs.forceNativeCelsys) { - latlng = wcs.eqToCelsys(latlng); - } else if (coord.nativeCelsys && wcs.forceNativeCelsys === false) { - latlng = wcs.celsysToEq(latlng); - } - this._map.panTo(latlng); - } - } else { - // If not, ask Sesame@CDS! - L.IIPUtils.requestURL( - this.options.sesameURL + '/-oI/A?' + str, - 'getting coordinates for ' + str, - this._getCoordinates, - this, - 10 - ); - } - }, - - _getCoordinates: function (_this, httpRequest) { - if (httpRequest.readyState === 4) { - if (httpRequest.status === 200) { - var str = httpRequest.responseText, - latlng = _this._map.options.crs.parseCoords(str); - - if (latlng) { - _this._map.panTo(latlng); - _this._onDrag(); - } else { - alert(str + ': Unknown location'); - } - } else { - alert( - 'There was a problem with the request to the Sesame service at CDS' - ); - } - } - }, + options: { + position: 'bottomleft', + title: 'Center coordinates. Click to change', + coordinates: [{ + label: 'RA, Dec', + units: 'HMS', + nativeCelsys: false + }], + centerQueryKey: 'center', + fovQueryKey: 'fov', + sesameURL: 'https://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame' + }, + + onAdd: function (map) { + // Create coordinate input/display box + var _this = this, + className = 'leaflet-control-wcs', + dialog = this._wcsdialog = L.DomUtil.create('div', className + '-dialog'), + coordSelect = L.DomUtil.create('select', className + '-select', dialog), + choose = document.createElement('option'), + coords = this.options.coordinates, + opt = [], + coordIndex; + + L.DomEvent.disableClickPropagation(coordSelect); + this._currentCoord = 0; + coordSelect.id = 'leaflet-coord-select'; + coordSelect.title = 'Switch coordinate system'; + for (var c in coords) { + opt[c] = document.createElement('option'); + opt[c].text = coords[c].label; + coordIndex = parseInt(c, 10); + opt[c].value = coordIndex; + if (coordIndex === 0) { + opt[c].selected = true; + } + coordSelect.add(opt[c], null); + } + + L.DomEvent.on(coordSelect, 'change', function (e) { + _this._currentCoord = coordSelect.value; + _this._onDrag(); + }); + + var input = this._wcsinput = L.DomUtil.create('input', className + '-input', dialog); + + L.DomEvent.disableClickPropagation(input); + input.type = 'text'; + input.title = this.options.title; + + // Speech recognition on WebKit engine + if ('webkitSpeechRecognition' in window) { + input.setAttribute('x-webkit-speech', 'x-webkit-speech'); + } + + map.on('move zoomend', this._onDrag, this); + L.DomEvent.on(input, 'focus', function () { + this.setSelectionRange(0, this.value.length); + }, input); + L.DomEvent.on(input, 'change', function () { + this.panTo(this._wcsinput.value); + }, this); + + // var clipboardbutton = L.DomUtil.create('div', className + '-clipboard', dialog); + // clipboardbutton.title = 'Copy to clipboard'; + // L.DomEvent.on(clipboardbutton, 'click', function () { + // var stateObj = {}, + // url = location.href, + // wcs = this._map.options.crs, + // latlng = map.getCenter(); + // L.IIPUtils.flashElement(this._wcsinput); + // url = L.IIPUtils.updateURL(url, this.options.centerQueryKey, + // L.IIPUtils.latLngToHMSDMS(latlng)); + // url = L.IIPUtils.updateURL(url, this.options.fovQueryKey, + // wcs.zoomToFov(map, map.getZoom(), latlng).toPrecision(4)); + // history.pushState(stateObj, '', url); + // L.IIPUtils.copyToClipboard(url); + // }, this); + + return this._wcsdialog; + }, + + onRemove: function (map) { + map.off('drag', this._onDrag); + }, + + _onDrag: function (e) { + var latlng = this._map.getCenter(), + wcs = this._map.options.crs, + coord = this.options.coordinates[this._currentCoord]; + + if (wcs.pixelFlag) { + this._wcsinput.value = latlng.lng.toFixed(0) + ' , ' + latlng.lat.toFixed(0); + } else { + if (!coord.nativeCelsys && wcs.forceNativeCelsys) { + latlng = wcs.celsysToEq(latlng); + } else if (coord.nativeCelsys && wcs.forceNativeCelsys === false) { + latlng = wcs.eqToCelsys(latlng); + } + switch (coord.units) { + case 'HMS': + this._wcsinput.value = L.IIPUtils.latLngToHMSDMS(latlng); + break; + case 'deg': + this._wcsinput.value = latlng.lng.toFixed(5) + ' , ' + latlng.lat.toFixed(5); + break; + default: + this._wcsinput.value = latlng.lng.toFixed(1) + ' , ' + latlng.lat.toFixed(1); + break; + } + } + }, + + panTo: function (str) { + var wcs = this._map.options.crs, + coord = this.options.coordinates[this._currentCoord], + latlng = wcs.parseCoords(str); + + if (latlng) { + if (wcs.pixelFlag) { + this._map.panTo(latlng); + } else { + if (!coord.nativeCelsys && wcs.forceNativeCelsys) { + latlng = wcs.eqToCelsys(latlng); + } else if (coord.nativeCelsys && wcs.forceNativeCelsys === false) { + latlng = wcs.celsysToEq(latlng); + } + this._map.panTo(latlng); + } + } else { + // If not, ask Sesame@CDS! + L.IIPUtils.requestURL(this.options.sesameURL + '/-oI/A?' + str, + 'getting coordinates for ' + str, this._getCoordinates, this, 10); + } + }, + + _getCoordinates: function (_this, httpRequest) { + if (httpRequest.readyState === 4) { + if (httpRequest.status === 200) { + var str = httpRequest.responseText, + latlng = _this._map.options.crs.parseCoords(str); + + if (latlng) { + _this._map.panTo(latlng); + _this._onDrag(); + } else { + alert(str + ': Unknown location'); + } + } else { + alert('There was a problem with the request to the Sesame service at CDS'); + } + } + } }); L.Map.mergeOptions({ - positionControl: false, + positionControl: false }); L.Map.addInitHook(function () { - if (this.options.positionControl) { - this.positionControl = new L.Control.MousePosition(); - this.addControl(this.positionControl); - } + if (this.options.positionControl) { + this.positionControl = new L.Control.MousePosition(); + this.addControl(this.positionControl); + } }); L.control.wcs = function (options) { - return new L.Control.WCS(options); + return new L.Control.WCS(options); }; /** @@ -7507,57 +6409,117 @@ L.control.wcs = function (options) { * LIneA - DRI integration */ L.Control.LineaOverlay = L.Control.extend({ - options: { - position: 'topleft', - title: 'Catalog Overlay', - forceSeparateButton: false, - }, - - onAdd: function (map) { - var className = 'leaflet-control-linea-overlay-catalog', - container; - - if (map.zoomControl && !this.options.forceSeparateButton) { - container = map.zoomControl._container; - } else { - container = L.DomUtil.create('div', 'leaflet-bar'); - } - - this._createButton( - this.options.title, - className, - container, - this.onClickLineaOverlayCatalog, - map - ); - - return container; - }, - - _createButton: function (title, className, container, fn, context) { - var link = L.DomUtil.create('a', className, container); - link.href = '#'; - link.title = title; - - L.DomEvent.addListener(link, 'click', L.DomEvent.stopPropagation) - .addListener(link, 'click', L.DomEvent.preventDefault) - .addListener(link, 'click', fn, context); - - return link; - }, - - onClickLineaOverlayCatalog: function () { - this.fire('overlaycatalog'); - }, + options: { + position: 'topleft', + title: 'Catalog Overlay', + forceSeparateButton: false + }, + + onAdd: function (map) { + var className = 'leaflet-control-linea-overlay-catalog', container; + + if (map.zoomControl && !this.options.forceSeparateButton) { + container = map.zoomControl._container; + } else { + container = L.DomUtil.create('div', 'leaflet-bar'); + } + + this._createButton(this.options.title, className, container, this.onClickLineaOverlayCatalog, map); + + return container; + }, + + _createButton: function (title, className, container, fn, context) { + var link = L.DomUtil.create('a', className, container); + link.href = '#'; + link.title = title; + + L.DomEvent + .addListener(link, 'click', L.DomEvent.stopPropagation) + .addListener(link, 'click', L.DomEvent.preventDefault) + .addListener(link, 'click', fn, context); + + + return link; + }, + + onClickLineaOverlayCatalog: function () { + + this.fire('overlaycatalog'); + + }, + }); L.Map.addInitHook(function () { - if (this.options.enableLineaOverlay) { - this.lineaoverlayControl = L.control.lineaoverlay(); - this.addControl(this.lineaoverlayControl); - } + if (this.options.enableLineaOverlay) { + + this.lineaoverlayControl = L.control.lineaoverlay(); + this.addControl(this.lineaoverlayControl); + } }); L.control.lineaoverlay = function (options) { - return new L.Control.LineaOverlay(options); + return new L.Control.LineaOverlay(options); +}; + + +/** + * This control is just a button that triggers the overlaycatalog event that will be used with the + * LIneA - DRI integration + */ +L.Control.LineaContrast = L.Control.extend({ + options: { + position: 'topleft', + title: 'Contrast', + forceSeparateButton: false + }, + + onAdd: function (map) { + var className = 'leaflet-control-linea-contrast', container; + + if (map.zoomControl && !this.options.forceSeparateButton) { + container = map.zoomControl._container; + } else { + container = L.DomUtil.create('div', 'leaflet-bar'); + } + + this._createButton(this.options.title, className, container, this.onClickLineaContrast, map); + + return container; + }, + + _createButton: function (title, className, container, fn, context) { + var link = L.DomUtil.create('a', className, container); + link.href = '#'; + link.title = title; + + L.DomEvent + .addListener(link, 'click', L.DomEvent.stopPropagation) + .addListener(link, 'click', L.DomEvent.preventDefault) + .addListener(link, 'click', fn, context); + + + return link; + }, + + onClickLineaContrast: function () { + + this.fire('changecontrast'); + + }, + +}); + +L.Map.addInitHook(function () { + if (this.options.enableLineaContrast) { + + this.lineacontrastControl = L.control.lineacontrast(); + this.addControl(this.lineacontrastControl); + } + +}); + +L.control.lineacontrast = function (options) { + return new L.Control.LineaContrast(options); }; diff --git a/frontend/eyeballing/public/visiomatic/visiomatic/dist/visiomatic.css b/frontend/eyeballing/public/visiomatic/visiomatic/dist/visiomatic.css index 7783800e9..2cfdd76c5 100644 --- a/frontend/eyeballing/public/visiomatic/visiomatic/dist/visiomatic.css +++ b/frontend/eyeballing/public/visiomatic/visiomatic/dist/visiomatic.css @@ -1046,26 +1046,26 @@ iframe.leaflet-control-iip-doc { .leaflet-control-minimap-toggle-display-bottomright { bottom: 0; - right: 0; + right: 0; } .leaflet-control-extramap-toggle-display-topleft{ top: 0; - left: 0; + left: 0; -webkit-transform: rotate(180deg); transform: rotate(180deg); } .leaflet-control-extramap-toggle-display-bottomleft{ bottom: 0; - left: 0; + left: 0; -webkit-transform: rotate(90deg); transform: rotate(90deg); } .leaflet-control-extramap-toggle-display-topright{ top: 0; - right: 0; + right: 0; -webkit-transform: rotate(270deg); transform: rotate(270deg); } @@ -1431,3 +1431,17 @@ a.leaflet-control-linea-overlay-catalog { /*background-repeat: no-repeat; background-position: center;**/ } + +/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ +/*%%%%%%%%%%%%%%%%%%%%%%%%%%%% Control.LineaOverlay %%%%%%%%%%%%%%%%%%%%%%%%%*/ +/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ +a.leaflet-control-linea-contrast { + background-image: url(images/icon-contrast.svg); + /*background-repeat: no-repeat; + background-position: center;**/ +} + +/* Destaca os icones da barra com fundo transparente */ +.leaflet-control-zoom { + filter: invert(1); +} \ No newline at end of file diff --git a/frontend/eyeballing/public/visiomatic/visiomatic/src/Catalog.js b/frontend/eyeballing/public/visiomatic/visiomatic/src/Catalog.js index 0e1423b8d..bae588d1d 100644 --- a/frontend/eyeballing/public/visiomatic/visiomatic/src/Catalog.js +++ b/frontend/eyeballing/public/visiomatic/visiomatic/src/Catalog.js @@ -119,20 +119,53 @@ L.Catalog['2MASS'] = L.extend({}, L.Catalog, { objurl: L.Catalog.vizierURL + '/VizieR-5?-source=II/246&-c={ra},{dec},eq=J2000&-c.rs=0.01' }); -L.Catalog.SDSS = L.extend({}, L.Catalog, { +L.Catalog['2MASS6X'] = L.extend({}, L.Catalog, { service: 'Vizier@CDS', - name: 'SDSS release 12', + name: '2MASS 6X', className: 'logo-catalog-vizier', - attribution: 'SDSS Photometric Catalog, Release 9 (Alam et al. 2015)', + attribution: '2MASS 6X Point Source Working Database / Catalog (Cutri+ 2006)', + color: 'red', + maglim: 24.0, + regionType: 'box', + url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=II/281/2mass6x&' + + '-out=2MASS,RAJ2000,DEJ2000,Jmag,e_Jmag,Hmag,e_Hmag,Kmag,e_Kmag,Bmag,Rmag&-out.meta=&' + + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&' + + '-out.max={nmax}&-sort=Jmag', + properties: ['', '', '', '', '', '', '', ''], + units: ['', '', '', '', '', '', '', ''], + objurl: L.Catalog.vizierURL + '/VizieR-5?-source=II/281/2mass6x&-c={ra},{dec},eq=J2000&-c.rs=0.01' +}); + +L.Catalog.SDSS9 = L.extend({}, L.Catalog, { + service: 'Vizier@CDS', + name: 'SDSS Release 9', + className: 'logo-catalog-vizier', + attribution: 'SDSS Photometric Catalog, Release 9 (Adelman-McCarthy+, 2012)', color: 'yellow', maglim: 25.0, regionType: 'box', - url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=V/147&' + - '-out=SDSS12,RA_ICRS,DE_ICRS,umag,gmag,rmag,imag,zmag&-out.meta=&' + + url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=V/139&' + + '-out=SDSS9,RA_ICRS,DE_ICRS,umag,gmag,rmag,imag,zmag&-out.meta=&' + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=rmag', properties: ['u', 'g', 'r', 'i', 'z'], units: ['', '', '', '', ''], - objurl: L.Catalog.vizierURL + '/VizieR-5?-source=V/147/sdss12&-c={ra},{dec},eq=J2000&-c.rs=0.01' + objurl: L.Catalog.vizierURL + '/VizieR-5?-source=V/139&-c={ra},{dec},eq=J2000&-c.rs=0.01' +}); + +L.Catalog.SDSS12 = L.extend({}, L.Catalog, { + service: 'Vizier@CDS', + name: 'SDSS Release 12', + className: 'logo-catalog-vizier', + attribution: 'SDSS Photometric Catalogue, Release 12 (Alam+, 2015)', + color: 'pink', + maglim: 25.0, + regionType: 'box', + url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=V/147&' + + '-out=SDSS12,RA_ICRS,DE_ICRS,umag,e_umag,gmag,e_gmag,rmag,e_rmag,imag,e_imag,zmag,e_zmag,zsp,e_zsp&-out.meta=&' + + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=gmag', + properties: ['u', 'g', 'r', 'i', 'z'], + units: ['', '', '', '', ''], + objurl: L.Catalog.vizierURL + '/VizieR-5?-source=V/147&-c={ra},{dec},eq=J2000&-c.rs=0.01' }); L.Catalog.PPMXL = L.extend({}, L.Catalog, { @@ -257,13 +290,29 @@ L.Catalog.GAIA_DR1 = L.extend({}, L.Catalog, { maglim: 20.0, regionType: 'box', url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=I/337&' + - '-out=Source,RA_ICRS,DE_ICRS,,pmRA,pmDE&-out.meta=&' + - '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=', + '-out=Source,RA_ICRS,DE_ICRS,Plx,e_Plx,pmRA,e_pmRA,pmDE,e_pmDE,,BPmag,e_BPmag,RPmag,e_RPmag,RV,e_RV&-out.meta=&' + + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=Gmag', properties: ['G', 'μɑ cos δ', 'μδ'], units: ['', 'mas/yr', 'mas/yr'], objurl: L.Catalog.vizierURL + '/VizieR-5?-source=I/337&-c={ra},{dec},eq=J2000&-c.rs=0.01' }); +L.Catalog.GAIA_DR2 = L.extend({}, L.Catalog, { + service: 'Vizier@CDS', + name: 'Gaia DR2', + className: 'logo-catalog-vizier', + attribution: 'Second Gaia Data Release (2018)', + color: 'purple', + maglim: 20.0, + regionType: 'box', + url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=I/345&' + + '-out=Source,RA_ICRS,DE_ICRS,Plx,e_Plx,pmRA,e_pmRA,pmDE,e_pmDE,Gmag,e_Gmag,BPmag,e_BPmag,RPmag,e_RPmag,RV,e_RV&-out.meta=&' + + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=Gmag', + properties: ['G', 'μɑ cos δ', 'μδ'], + units: ['', 'mas/yr', 'mas/yr'], + objurl: L.Catalog.vizierURL + '/VizieR-5?-source=I/345&-c={ra},{dec},eq=J2000&-c.rs=0.01' +}); + L.Catalog.URAT_1 = L.extend({}, L.Catalog, { service: 'Vizier@CDS', name: 'URAT1', @@ -323,7 +372,7 @@ L.Catalog.GLEAM = L.extend({}, L.Catalog, { '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=-Fintwide', properties: ['Fint(170-231MHz)', 'Major axis FWHM', 'Minor axis FWHM', 'Position angle'], units: ['Jy', '″', '″', '°'], - objurl: L.Catalog.vizierURL + '/VizieR-5?-source=-source=VIII/100/gleamegc&-c={ra},{dec},eq=J2000&-c.rs=0.2', + objurl: L.Catalog.vizierURL + '/VizieR-5?-source=VIII/100/gleamegc&-c={ra},{dec},eq=J2000&-c.rs=0.2', draw: function (feature, latlng) { return L.ellipse(latlng, { majAxis: feature.properties.items[1] / 3600.0, @@ -346,7 +395,7 @@ L.Catalog.TGSS = L.extend({}, L.Catalog, { '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=-Stotal', properties: ['Fpeak(150MHz)', 'Major axis FWHM', 'Minor axis FWHM', 'Position angle'], units: ['mJy', '″', '″', '°'], - objurl: L.Catalog.vizierURL + '/VizieR-3?-source=-source=J/A%2bA/598/A78/table3&-c={ra},{dec},eq=J2000&-c.rs=0.2', + objurl: L.Catalog.vizierURL + '/VizieR-3?-source=J/A%2bA/598/A78/table3&-c={ra},{dec},eq=J2000&-c.rs=0.2', draw: function (feature, latlng) { return L.ellipse(latlng, { majAxis: feature.properties.items[1] / 7200.0, @@ -355,3 +404,35 @@ L.Catalog.TGSS = L.extend({}, L.Catalog, { }); } }); + +L.Catalog.PS1 = L.extend({}, L.Catalog, { + service: 'Vizier@CDS', + name: 'PS1', + className: 'logo-catalog-vizier', + attribution: 'The Pan-STARRS release 1 (PS1) Survey - DR1 (Chambers+, 2016)', + color: 'blue', + maglim: 30.0, + regionType: 'box', + url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=II/349&' + + '-out=objID,RAJ2000,DEJ2000,gmag,e_gmag,rmag,e_rmag,imag,e_imag,zmag,e_zmag,ymag_e_ymag,gKmag,e_gKmag,rKmag,e_rKmag,iKmag,e_iKmag,zKmag,e_zKmag,yKmag,e_yKmag&-out.meta=&' + + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=-gmag', + properties: ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], + units: ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], + objurl: L.Catalog.vizierURL + '/VizieR-3?-source=II/349&-c={ra},{dec},eq=J2000&-c.rs=0.2', +}); + +L.Catalog.HSC2 = L.extend({}, L.Catalog, { + service: 'Vizier@CDS', + name: 'HSC2', + className: 'logo-catalog-vizier', + attribution: 'Hubble Source Catalog (V1 and V2) (Whitmore+, 2016)', + color: 'blue', + maglim: 30.0, + regionType: 'box', + url: L.Catalog.vizierURL + '/asu-tsv?&-mime=csv&-source=II/342/hsc2&' + + '-out=Source,RAJ2000,DEJ2000,Filter,magAper2,magAuto&-out.meta=&' + + '-c.eq={sys}&-c={lng},{lat}&-c.bd={dlng},{dlat}&-out.max={nmax}&-sort=-magAuto', + properties: ['', '', '',], + units: ['', '', ''], + objurl: L.Catalog.vizierURL + '/VizieR-3?-source=II/342/hsc2&-c={ra},{dec},eq=J2000&-c.rs=0.2', +}); \ No newline at end of file diff --git a/frontend/eyeballing/src/api/Api.js b/frontend/eyeballing/src/api/Api.js index 3e99fafe9..25980f5e7 100644 --- a/frontend/eyeballing/src/api/Api.js +++ b/frontend/eyeballing/src/api/Api.js @@ -176,7 +176,7 @@ class DriApi { export default DriApi; export function toLogin() { - window.location.replace(`${api}/api-auth/login/?next=/eyeballing/`); + window.location.replace(`${api}/api-auth/login/?next=/tile_viewer/`); } export function logout() { diff --git a/frontend/eyeballing/src/components/ChooseFilterDialog.js b/frontend/eyeballing/src/components/ChooseFilterDialog.js index 2523e1858..708919d07 100644 --- a/frontend/eyeballing/src/components/ChooseFilterDialog.js +++ b/frontend/eyeballing/src/components/ChooseFilterDialog.js @@ -12,69 +12,69 @@ import Divider from '@material-ui/core/Divider'; import { makeStyles } from '@material-ui/core/styles'; const useStyles = makeStyles(theme => ({ - closeButton: { - position: 'absolute', - right: theme.spacing(1), - top: theme.spacing(1), - color: theme.palette.grey[500], - }, - closeIcon: { - fontSize: '1rem', - }, + closeButton: { + position: 'absolute', + right: theme.spacing(1), + top: theme.spacing(1), + color: theme.palette.grey[500], + }, + closeIcon: { + fontSize: '1rem', + }, })); function ChooseFilterDialog(props) { - const { selectedValue } = props; - const classes = useStyles(); + const { selectedValue } = props; + const classes = useStyles(); - function handleClose() { - props.handleClose(selectedValue); - } + function handleClose() { + props.handleClose(selectedValue); + } - function handleChange(event, newValue) { - props.handleClose(newValue); - } + function handleChange(event, newValue) { + props.handleClose(newValue); + } - return ( - - - Filter the list of Tiles - - - - - - } - label={'All'} - /> - } - label={'List good tiles'} - /> - } - label={'List bad tiles'} - /> - } - label={'List of tiles not inspected'} - /> - - - - ); + return ( + + + Filter the list of Tiles + + + + + + } + label="All" + /> + } + label="List good tiles" + /> + } + label="List bad tiles" + /> + } + label="List of tiles not inspected" + /> + + + + ); } ChooseFilterDialog.propTypes = { - handleClose: PropTypes.func.isRequired, - open: PropTypes.bool.isRequired, - selectedValue: PropTypes.string.isRequired, + handleClose: PropTypes.func.isRequired, + open: PropTypes.bool.isRequired, + selectedValue: PropTypes.string.isRequired, }; -export default ChooseFilterDialog; \ No newline at end of file +export default ChooseFilterDialog; diff --git a/frontend/eyeballing/src/components/Counter.js b/frontend/eyeballing/src/components/Counter.js index 293bc8a05..174a9c003 100644 --- a/frontend/eyeballing/src/components/Counter.js +++ b/frontend/eyeballing/src/components/Counter.js @@ -25,28 +25,34 @@ const useStyles = makeStyles(theme => ({ export default function Counter(props) { const classes = useStyles(); + const { + total, + good, + bad, + } = props.counts; + + const { hasInspection } = props; return (
- - Tiles: - - - {props.counts.tiles ? props.counts.tiles : 0} - - - {props.hasInspection ? ( + {hasInspection ? ( <> + + Examined: + + + {good + bad} + - {props.counts.true ? props.counts.true : 0} + {`${Math.round((((good * 100) / total) + Number.EPSILON) * 100) / 100}%`} - {props.counts.false ? props.counts.false : 0} + {`${Math.round((((bad * 100) / total) + Number.EPSILON) * 100) / 100}%`} - + + ) : ( + <> - Not: + Total: - {props.counts.null ? props.counts.null : 0} + {total} - - ) : null} + )}
); } Counter.propTypes = { - counts: PropTypes.object.isRequired, + counts: PropTypes.shape({ + total: PropTypes.number, + good: PropTypes.number, + bad: PropTypes.number, + }).isRequired, hasInspection: PropTypes.bool.isRequired, }; diff --git a/frontend/eyeballing/src/components/Header.js b/frontend/eyeballing/src/components/Header.js index 7b04980fa..a253c7de6 100644 --- a/frontend/eyeballing/src/components/Header.js +++ b/frontend/eyeballing/src/components/Header.js @@ -8,8 +8,11 @@ import Typography from '@material-ui/core/Typography'; import HomeIcon from '@material-ui/icons/Home'; import MenuItem from '@material-ui/core/MenuItem'; import Menu from '@material-ui/core/Menu'; +import Divider from '@material-ui/core/Divider'; import MenuIcon from '@material-ui/icons/Menu'; import HelpIcon from '@material-ui/icons/Help'; +import HelpOutlineIcon from '@material-ui/icons/HelpOutline'; +import LogoutIcon from '@material-ui/icons/ExitToApp'; import SelectReleases from './SelectReleases'; import logo from '../assets/img/icon-des.png'; import { logout } from '../api/Api'; @@ -29,10 +32,14 @@ const styles = theme => ({ }, username: { marginLeft: theme.spacing(2), + marginRight: theme.spacing(2), }, menuButton: { marginRight: theme.spacing(1), }, + menuIcon: { + marginRight: theme.spacing(1), + }, }); function Header(props) { @@ -56,10 +63,10 @@ function Header(props) { logout(); } - function handleHomeEyeballing() { + function handleHomeTileViewer() { const { protocol } = window.location; const { host } = window.location; - const location = `${protocol}//${host}/eyeballing`; + const location = `${protocol}//${host}/tile_viewer/`; window.location.assign(location); } @@ -76,15 +83,27 @@ function Header(props) { setAnchorEl(null); } - function handleHelp() { + function handleTutorials() { setTutorialOpen(true); } + function handleHelp() { + const { protocol } = window.location; + const { host } = window.location; + const location = `${protocol}//${host}/contact-us/`; + + console.log('location', location); + + handleClose(); + + window.open(location); + } + return ( - + logo DES @@ -112,15 +131,6 @@ function Header(props) { - - - - - About LIneA - Logout + + About LIneA + + + + Tutorials + + + + Help + + + + + + Logout + diff --git a/frontend/eyeballing/src/components/TutorialDialog.js b/frontend/eyeballing/src/components/TutorialDialog.js index 8a3b7fb9c..d35088ce1 100644 --- a/frontend/eyeballing/src/components/TutorialDialog.js +++ b/frontend/eyeballing/src/components/TutorialDialog.js @@ -48,7 +48,7 @@ function TutorialDialog({ - {data.map(row => ( + {data.length > 0 ? data.map(row => (
@@ -69,7 +69,11 @@ function TutorialDialog({ - ))} + )) : ( + + There is no tutorial registered for this application. + + )} diff --git a/frontend/eyeballing/src/components/visiomatic/ContrastMenu.js b/frontend/eyeballing/src/components/visiomatic/ContrastMenu.js new file mode 100644 index 000000000..631273578 --- /dev/null +++ b/frontend/eyeballing/src/components/visiomatic/ContrastMenu.js @@ -0,0 +1,78 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import Dialog from '@material-ui/core/Dialog'; +import RadioGroup from '@material-ui/core/RadioGroup'; +import Radio from '@material-ui/core/Radio'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import DialogContent from '@material-ui/core/DialogContent'; +import IconButton from '@material-ui/core/IconButton'; +import CloseIcon from '@material-ui/icons/Close'; +import { makeStyles } from '@material-ui/core/styles'; + +const useStyles = makeStyles(theme => ({ + closeButton: { + position: 'absolute', + right: theme.spacing(1), + top: theme.spacing(1), + color: theme.palette.grey[500], + }, + closeIcon: { + fontSize: '1rem', + }, + dialogTitle: { + padding: '0px 0 5px', + }, + root: { + zIndex: '9999 !important', + }, +})); + +function ContrastMenu({ + open, + currentContrast, + handleChange, + handleClose, +}) { + const classes = useStyles(); + + + return ( + + + Choose Color Ranges + + + + + + + } + label="Default Contrast" + /> + } + label="Medium Contrast" + /> + } + label="High Contrast" + /> + + + + ); +} + +ContrastMenu.propTypes = { + open: PropTypes.bool.isRequired, + currentContrast: PropTypes.string.isRequired, + handleChange: PropTypes.func.isRequired, + handleClose: PropTypes.func.isRequired, +}; + +export default ContrastMenu; diff --git a/frontend/eyeballing/src/components/visiomatic/Visiomatic.js b/frontend/eyeballing/src/components/visiomatic/Visiomatic.js index ff88bdd66..6133a01a7 100644 --- a/frontend/eyeballing/src/components/visiomatic/Visiomatic.js +++ b/frontend/eyeballing/src/components/visiomatic/Visiomatic.js @@ -3,6 +3,7 @@ import './Viewer.css'; import { uniqueId } from 'lodash'; import PropTypes from 'prop-types'; import ContextMenu from './ContextMenu'; +import ContrastMenu from './ContrastMenu'; import circle from './circle-outline.svg'; const colorRanges = { @@ -93,6 +94,9 @@ class VisiomaticPanel extends Component { contextMenuUpdateOpen: false, contextMenuEvt: null, points: [], + currentContrast: 'defaultContrast', + contrastMenuOpen: false, + preventUpdateContrast: false, // This is a flag to prevent the tile contrast from changing when the image changes }; } @@ -211,40 +215,115 @@ class VisiomaticPanel extends Component { return lCatalog; }; - componentDidMount() { - const map = this.libL.map(this.id, { fullscreenControl: true, zoom: 1 }); + onContrastMenuOpen = () => { + if (this.props.currentDataset) { + this.setState({ + contrastMenuOpen: true, + }); + } + } + + onContrastMenuClose = () => { + this.setState({ + contrastMenuOpen: false, + }); + } + + onContrastMenuChange = (e, value) => { + this.setState({ + contrastMenuOpen: false, + currentContrast: value, + preventUpdateContrast: false, + }); + } - this.libL.control.scale.wcs({ pixels: false }).addTo(map); - this.libL.control.reticle().addTo(map); + // showContrastWindow = () => { + // this.setState({ + // contrastWindowOpen: true, + // colorRanges, + // }); - this.wcsControl = this.libL.control - .wcs({ - coordinates: [{ label: 'RA,Dec', units: 'HMS' }], - position: 'topright', - }) - .addTo(map); + // const me = this; + // const currentContrast = me.getCurrentContrast(); - // Add a Reticle to Map - this.libL.control.reticle().addTo(map); + // // TODO: verificar se o valor de currentContrast esta disponivel na lista de Contrasts disponiveis. + // if (currentContrast !== null) { - const sidebar = this.libL.control.sidebar().addTo(map); + // this.setState({ + // contrastWindowOpen: true, + // colorRanges, + // }); - // Channel Mixing - this.libL.control.iip.channel().addTo(sidebar); + // me.setCurrentContrast(currentContrast); - // Image Preference - this.libL.control.iip.image().addTo(sidebar); + // } else { + // return false; + // } + // } - map.on('layeradd', this.onLayerAdd, this); - map.on('layerremove', this.onLayerRemove, this); +changeContrast = () => { + const me = this; - if (this.props.hasInspection) { - map.on('contextmenu', this.onContextMenuOpen, this); - map.on('overlaycatalog', this.overlayCatalog, this); - } - this.map = map; - // this.changeImage(); + const imageLayer = me.layer; + + const minValues = colorRanges[this.state.currentContrast].minMaxValues.map(v => v[0]); + const maxValues = colorRanges[this.state.currentContrast].minMaxValues.map(v => v[1]); + + imageLayer.iipMinValue = minValues; + imageLayer.iipMaxValue = maxValues; + + imageLayer.updateMix(); + imageLayer.redraw(); +} + +componentDidMount() { + const map = this.libL.map(this.id, { + fullscreenControl: true, + zoom: 1, + enableLineaContrast: true, + }); + + this.libL.control.scale.wcs({ pixels: false }).addTo(map); + this.libL.control.reticle().addTo(map); + + this.wcsControl = this.libL.control + .wcs({ + coordinates: [ + { + label: 'RA, Dec (Deg)', + units: 'deg', + }, + { + label: 'RA,Dec (HMS)', + units: 'HMS', + }, + ], + position: 'topright', + }) + .addTo(map); + + // Add a Reticle to Map + this.libL.control.reticle().addTo(map); + + const sidebar = this.libL.control.sidebar().addTo(map); + + // Channel Mixing + this.libL.control.iip.channel().addTo(sidebar); + + // Image Preference + this.libL.control.iip.image().addTo(sidebar); + + map.on('layeradd', this.onLayerAdd, this); + map.on('layerremove', this.onLayerRemove, this); + map.on('changecontrast', this.onContrastMenuOpen, this); + + if (this.props.hasInspection) { + map.on('contextmenu', this.onContextMenuOpen, this); + map.on('overlaycatalog', this.overlayCatalog, this); } + this.map = map; + // this.changeImage(); +} removeImageLayer = () => { @@ -261,7 +340,7 @@ class VisiomaticPanel extends Component { } } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps, prevState) { if (prevProps.image !== this.props.image) { this.changeImage(); } @@ -276,6 +355,10 @@ class VisiomaticPanel extends Component { if (this.props.hasInspection && prevProps.contrast !== this.props.contrast) { this.changeImage(); } + + if (!this.state.preventUpdateContrast && this.state.currentContrast !== prevState.currentContrast) { + this.changeContrast(); + } } @@ -358,6 +441,11 @@ class VisiomaticPanel extends Component { // ], }) .addTo(this.map); + + this.setState({ + currentContrast: 'defaultContrast', + preventUpdateContrast: true, + }); } else if (this.layer) { this.map.removeLayer(this.layer); } @@ -409,6 +497,10 @@ class VisiomaticPanel extends Component { render() { const { hasInspection } = this.props; + const toolbar = 64; + const footer = 64; + const padding = 21; + // Ajuste no Tamanho do container return ( <> @@ -417,7 +509,7 @@ class VisiomaticPanel extends Component { className="visiomatic-container" style={{ width: '100%', - height: 'calc(100vh - 150px)', + height: window.innerHeight - toolbar - footer - padding, // height: '100%', }} /> @@ -433,6 +525,12 @@ class VisiomaticPanel extends Component { reloadData={this.props.reloadData} /> ) : null} + ); } diff --git a/frontend/eyeballing/src/home.js b/frontend/eyeballing/src/home.js index 96a57ce82..75b566422 100644 --- a/frontend/eyeballing/src/home.js +++ b/frontend/eyeballing/src/home.js @@ -58,12 +58,12 @@ const useStyles = makeStyles(theme => ({ visiomatic: { backgroundColor: theme.palette.grey[200], }, - tilelist: { - height: '100%', + tilelist: props => ({ + height: props.tileHeight, textAlign: 'center', minWidth: 300, position: 'relative', - }, + }), tilesCount: { textAlign: 'left', }, @@ -133,6 +133,13 @@ const useStyles = makeStyles(theme => ({ padding: theme.spacing(1), }, }, + cardVisiomatic: { + height: window.innerHeight - 64 - 64 - 21, + }, + noResults: { + fontWeight: 'bold', + lineHeight: 2.5, + }, })); function Home() { @@ -148,9 +155,9 @@ function Home() { const [menuContrastOpen, setMenuContrastOpen] = useState(false); const [contrast, setContrast] = useState('defaultContrast'); const [counts, setCounts] = useState({ - true: 0, - false: 0, - null: 0, + total: 0, + good: 0, + bad: 0, }); const [showFilterDialog, setShowFilterDialog] = useState(false); const [filterInspect, setFilterInspect] = useState(''); @@ -166,11 +173,20 @@ function Home() { const [searchEnabled, setSearchEnabled] = useState(false); const [visiomaticCenter, setVisiomaticCenter] = useState([]); const [fov, setFov] = useState(2); + const [selectedLine, setSelectedLine] = useState(null); + const [noResultsFound, setNoResultsFound] = useState(false); const searchRef = useRef(''); + const header = 64; + const toolbar = 64; + const footer = 64; + const tilesCount = 40; + const containerPadding = 32; const api = new DriApi(); - const classes = useStyles(); + const classes = useStyles({ + tileHeight: window.innerHeight - header - footer - 21, + }); useEffect(() => { api.getTileInspectionOption().then(res => setHasInspection(res.TILE_VIEWER_INSPECTION_ENABLED)); @@ -192,7 +208,7 @@ function Home() { setCurrentRelease(release); }); - api.getTutorial().then(res => setTutorial(res)); + api.getTutorial().then(res => setTutorial(res)).catch(() => setTutorial([])); }, []); useEffect(() => { @@ -201,10 +217,15 @@ function Home() { if (hasInspection) { // Totais de Tiles boas, ruim e não inspecionadas const goodTiles = countBy(res, el => el.isp_value); - goodTiles.tiles = res.length; - setCounts(goodTiles); + goodTiles.total = res.length; + + setCounts({ + total: goodTiles.total, + good: goodTiles.true ? goodTiles.true : 0, + bad: goodTiles.false ? goodTiles.false : 0, + }); } else { - setCounts({ tiles: res.length }); + setCounts(prevCounts => ({ ...prevCounts, total: res.length })); } if (allTiles.length === 0) { setAllTiles(res); @@ -215,6 +236,8 @@ function Home() { }, [hasInspection, currentRelease, filterInspect, loadingAllTiles]); const loadMoreDatasets = () => { + setNoResultsFound(false); + if (searchRef.current && searchRef.current.value.split(',').length > 1) { return; } @@ -249,6 +272,17 @@ function Home() { datasetLoading.current = false; } setLoadingList(false); + + if (datasetTotalCount === 0) { + setNoResultsFound(true); + } + }) + .catch(() => { + setTotalCount(0); + setDatasets([]); + datasetLoading.current = false; + setLoadingList(false); + setNoResultsFound(true); }); }; @@ -270,27 +304,27 @@ function Home() { setCounts({}); }; - const onSelectDataset = dataset => setCurrentDataset(dataset); - - useEffect(() => { - if (Object.keys(currentDataset).length > 0) { - const searchSplit = searchRef.current.value.split(','); - - if (searchSplit.length === 2) { - setVisiomaticCenter([ - searchSplit[0], - searchSplit[1], - ]); - setFov(0.3); - } else { - setVisiomaticCenter([ - currentDataset.tli_ra, - currentDataset.tli_dec, - ]); - setFov(2); + const onSelectDataset = (dataset, isKeyboard) => { + if (dataset) { + if (!isKeyboard) { + datasets.forEach((row, i) => { + if (row.id === dataset.id) { + setSelectedLine(i); + } + }); } + + setCurrentDataset(dataset); + + // It is necessary to have a center, + // otherwise the visiomatic gets lost in rendering the tile + setVisiomaticCenter([ + dataset.tli_ra, + dataset.tli_dec, + ]); + setFov(2); } - }, [currentDataset]); + }; const handleClickSnackBar = () => setOpenSnackBar(!openSnackBar); @@ -308,7 +342,10 @@ function Home() { const getDatasetCommentsByType = () => { setCommentsWithFeature([]); - return api.getDatasetCommentsByType(currentDataset.id, 2).then(res => setCommentsWithFeature(res)); + if (currentDataset.id) { + api.getDatasetCommentsByType(currentDataset.id, 2) + .then(res => setCommentsWithFeature(res)); + } }; useEffect(() => { @@ -403,9 +440,9 @@ function Home() { const handleMenuFilterOpen = () => setShowFilterDialog(true); const handleMenuFilterClose = (value) => { + setShowFilterDialog(false); if (value !== filterInspect) { setFilterInspect(value); - setShowFilterDialog(false); setTotalCount(0); reloadList(); } @@ -436,10 +473,6 @@ function Home() { uraur -= 360; } - // tli_urall < ra - // AND tli_udecll < dec - // AND tli_uraur > ra - // AND tli_udecur > dec if (urall < ra && tile.tli_udecll < dec && uraur > ra && tile.tli_udecur > dec) { result.push(tile); return false; @@ -449,26 +482,127 @@ function Home() { return result; }; + const sexagesimal2decimal = (sexagesimal) => { + let grau = 0; + let min = 0; + let sec = 0; + + const data = sexagesimal.split(':'); + let sign = 0; + + if (data[0] < 0) { + sign = -1; + grau = parseFloat(data[0] / 1) * -1; + } else { + sign = 1; + grau = parseFloat(data[0] / 1); + } + + min = parseFloat(data[1] / 60.0); + sec = parseFloat(data[2] / 3600.0) || 0; + + const dec = ((grau + min + sec) * sign).toFixed(4); + + return dec; + }; + const handleInputSearch = () => { - const searchSplit = searchRef.current.value.split(','); + setNoResultsFound(false); + const { value } = searchRef.current; + const splitByComma = value.split(','); + const splitBySpace = value.split(' '); + let splitRaDec = null; + let splitByHms = null; + + + // First the split by space, because the string could have a ", ". + // In this case, the split by comma should have priority + if (splitBySpace.length === 2) { + // Force a conversion of each value to a Number + // and check if it doesn't return a NaN + if (!isNaN(Number(splitBySpace[0].trim())) && !isNaN(Number(splitBySpace[1].trim()))) { + splitRaDec = [ + parseFloat(splitBySpace[0].trim()), + parseFloat(splitBySpace[1].trim()), + ]; + + // If it does return a NaN but each value splitted by ":" has three elements + } else if (splitBySpace[0].split(':').length === 3 && splitBySpace[1].split(':').length === 3) { + splitByHms = [splitBySpace[0].trim(), splitBySpace[1].trim()]; + } + } + + if (splitByComma.length === 2) { + // Force a conversion of each value to a Number + // and check if it doesn't return a NaN + if (!isNaN(Number(splitByComma[0].trim())) && !isNaN(Number(splitByComma[1].trim()))) { + splitRaDec = [ + parseFloat(splitByComma[0].trim()), + parseFloat(splitByComma[1].trim()), + ]; + + // If it does return a NaN but each value splitted by ":" has three elements + } else if (splitByComma[0].split(':').length === 3 && splitByComma[1].split(':').length === 3) { + splitByHms = [splitByComma[0].trim(), splitByComma[1].trim()]; + } + } + + if (splitRaDec) { + const datasetByPosition = filterByRaDec(splitRaDec[0], splitRaDec[1]); + + if (datasetByPosition.length > 0) { + datasetLoading.current = true; + setDatasets(datasetByPosition); + setTotalCount(datasetByPosition.length); + datasetLoading.current = false; - if (searchSplit.length === 2) { - const datasetByPosition = filterByRaDec( - parseFloat(searchSplit[0]), - parseFloat(searchSplit[1]), - ); + // If only one dataset was found, then select it automatically + if (datasetByPosition.length === 1) { + setCurrentDataset(datasetByPosition[0]); + setFov(0.05); + setVisiomaticCenter(splitRaDec); + } + } else { + setDatasets([]); + setTotalCount(0); + setNoResultsFound(true); + setLoadingList(false); + setCurrentDataset({}); + } + } else if (splitByHms) { + // If the search is by Hms, convert each one to degree + // and apply the same search as the if above + const raDec = [sexagesimal2decimal(splitByHms[0]), sexagesimal2decimal(splitByHms[1])]; + + const datasetByPosition = filterByRaDec(raDec[0], raDec[1]); if (datasetByPosition.length > 0) { datasetLoading.current = true; setDatasets(datasetByPosition); setTotalCount(datasetByPosition.length); datasetLoading.current = false; + + // If only one dataset was found, then select it automatically + if (datasetByPosition.length === 1) { + setCurrentDataset(datasetByPosition[0]); + setFov(0.05); + setVisiomaticCenter(raDec); + } + } else { + setDatasets([]); + setTotalCount(0); + setNoResultsFound(true); + setCurrentDataset({}); + setLoadingList(false); } } else { + // Else includes the tilename. I could check for DES or make a regex for it, + // but a more broad approach allows the user to find the tiles with +0209, for example. reloadList(); } }; + const handleDelete = commentId => api.deleteComment(commentId).then(() => { handleComment(currentDataset); reloadList(); @@ -497,10 +631,11 @@ function Home() { }; const onChangeRelease = (value) => { - setLoadingAllTiles(true); - setCurrentRelease(value); reloadList(); reloadAllTiles(); + setCurrentDataset({}); + setLoadingAllTiles(true); + setCurrentRelease(value); }; useEffect(() => { @@ -511,60 +646,65 @@ function Home() { } }, [allTiles]); - const Rows = () => datasets.map(dataset => ( - { - onSelectDataset(dataset); - }} - divider - selected={dataset.id === currentDataset.id} - > - 0 ? classes.datasetWithComment : null} - onClick={(e) => { - e.stopPropagation(); - e.preventDefault(); - handleComment(dataset); - }} - > - {`${dataset.comments} comments`} - - ) : null} - /> - - {hasInspection ? ( - <> - qualifyDataset(dataset, 'ok')}> - {dataset.isp_value ? ( - - ) : ( - - )} - - qualifyDataset(dataset, 'notok')}> - {dataset.isp_value === false ? ( - - ) : ( - - )} - - handleComment(dataset)}> - + const Rows = () => { + if (datasets.length > 0) { + return datasets.map(dataset => ( + { + onSelectDataset(dataset); + }} + divider + selected={dataset.id === currentDataset.id} + > + 0 ? classes.datasetWithComment : null} + onClick={(e) => { + e.stopPropagation(); + e.preventDefault(); + handleComment(dataset); + }} + > + {`${dataset.comments} comments`} + + ) : null} + /> + + {hasInspection ? ( + <> + qualifyDataset(dataset, 'ok')}> + {dataset.isp_value ? ( + + ) : ( + + )} + + qualifyDataset(dataset, 'notok')}> + {dataset.isp_value === false ? ( + + ) : ( + + )} + + handleComment(dataset)}> + + + + ) : null} + handleDownloadClick(dataset)}> + - - ) : null} - handleDownloadClick(dataset)}> - - - - - )); + + + )); + } + return noResultsFound && No results were found; + }; const handleDownloadClose = () => { setDownloadInfo({ @@ -572,11 +712,34 @@ function Home() { }); }; - const header = 64; - const toolbar = 64; - const footer = 64; - const tilesCount = 40; - const containerPadding = 32; + useEffect(() => { + const handleKeyUp = (e) => { + // 38 (ArrowUp) + if (e.keyCode === 38) { + const row = selectedLine !== null + && selectedLine !== 0 ? selectedLine - 1 : 0; + + setSelectedLine(row); + } + + // 40 (ArrowDown) + if (e.keyCode === 40) { + const row = selectedLine !== null + && selectedLine !== datasets.length ? selectedLine + 1 : 0; + + setSelectedLine(row); + } + }; + + window.addEventListener('keyup', handleKeyUp); + return () => window.removeEventListener('keyup', handleKeyUp); + }, [datasets, selectedLine]); + + useEffect(() => { + if (selectedLine !== null) { + onSelectDataset(datasets[selectedLine], true); + } + }, [selectedLine]); return ( @@ -588,10 +751,10 @@ function Home() { currentRelease={currentRelease} onChangeRelease={onChangeRelease} /> - } /> + } /> (
@@ -624,7 +787,7 @@ function Home() { - + @@ -672,7 +835,7 @@ function Home() { - + {currentRelease !== '' ? ( {hasInspection ? ( ( + + LIneA Science Server + + + + - - - - LIneA Science Server + + + diff --git a/frontend/landing_page/src/Routes/index.js b/frontend/landing_page/src/Routes/index.js index 6764f05d0..16058f16c 100644 --- a/frontend/landing_page/src/Routes/index.js +++ b/frontend/landing_page/src/Routes/index.js @@ -7,6 +7,7 @@ import AboutUs from '../pages/AboutUs'; import Help from '../pages/Help'; import Tutorials from '../pages/Tutorials'; import Contact from '../pages/Contact'; +import OracleEasyAccess from '../pages/OracleEasyAccess'; import Notfound from '../pages/NotFound'; function Router() { @@ -19,6 +20,7 @@ function Router() { +