-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Nunaliit is an open source web mapping framework, designed for a Linux operating system (OS) and uses a number of open source software packages. However, this tutorial recognizes that most users are coming from a Windows OS background, and are not accustom to working with Linux.
The first section of this tutorial instructs how to install Nunaliit on a virtual machine (Optional), which is available to deploy in most OS environments, including; Windows, Mac OS or various Linux distributions. Using a virtual machine allows users to try Nunaliit without forcing them to completely abandon their OS of choice, creating a Linux partition, or needing to invest in a server to see Nunaliit’s potential as a free web mapping framework.
If you’re currently a Linux user and have Ubuntu installed on your desktop or server environment and don't want to install a Virtual Machine, you can skip steps 1 and 2 and start on step 3. Note: Nunaliit officially supports being run on Ubuntu, but can also work on other distributions.
Before you can install a new Linux OS as a virtual machine, you will need to download a copy of the OS. For this exercise we must download the popular distribution Ubuntu, version 18.04 LTS. Nunaliit can run in Ubuntu version 20.04 but this tutorial and our prerequisite installation documentation has not caught up yet so you won't have a seamless path. Note: If you're familiar with Linux, you may want to download the server version of Ubuntu instead of the desktop version, if you have no need for a graphical user interface for your virtual machine.
When downloading the distribution, make sure it’s in an iso file format, and is supported by your hardware (note: if the 64 bit OS is not possible in VirtualBox, then you will likely need to use the 32 bit version of Ubuntu).
With a copy of Ubuntu downloaded, you will now create a virtual machine using the popular open source software VirtualBox.First you will need to download VirtualBox, a free piece of software allowing you to create virtual machines within your own OS (i.e. you can run a copy of Ubuntu within Windows, MacOS or Linux environment). Go to https://www.virtualbox.org/, to download the software required for your computer, and install it on your system.
Once successfully installed, run VirtualBox and click the New button.
Provide the name of the virtual machine, the VM folder, and select the correct type and version. In our case it will be;
-
Name: Ubuntu 18.04
-
Machine Folder: the_directory_you_wish_to_store_vms/VirtualBox VMs
-
Type: Linux
-
Version: Ubuntu (64bit) (Note: this may be 32 bit depending on Step 1)
Next you will define the amount of memory available to the virtual machine. Note: A minimum of 4096 MB should be used for Ubuntu 18.04. (Note you can change this value afterwards through the settings.
Finally you will need to create a virtual hard disk now by selecting ‘Create a virtual hard disk now’ and click Create.
Define the location to save the virtual hard disk image (typically within the VirtualBox VMs directory defined in the previous step).
Select the amount of hard drive space to allocate (10.00 GB is the default). If you can provide additional space this is recommended.
Next select the hard disk file type: 'VDI VirtualBox disk image', and click the 'Dynamically allocated' Storage on physical hard disk option, and lastly click Next.
Select the virtual machine you wish to run (if you only have one, it will automatically be selected). Under the Storage section click '[Optical Drive] Empty', select the option 'Choose disk image ...' and then select the Ubuntu iso file you downloaded in Step 1. Lastly click the Start button. Note: If you see an error message stating, ‘FATAL: No bootable medium found! System halted.’ This likely means you need to select the iso file you downloaded in Step 1 to be booted.
After a couple minutes, an Ubuntu install window should pop up. Next, click the Install Ubuntu button.
Define the keyboard layout, and click the Continue button.
Check both the 'Normal installation' box and 'Download updates while installing Ubuntu' box on the Updates and other software screen and click the Continue button
Select the option Erase disk and install Ubuntu, and then click the Install Now button.
You will then be asked to confirm you want to write these changes to disks, and you will need to click Continue.
Define where you are in the world (this will set your timezone/clock on the desktop) and then click Continue.
Provide your user information for your user account in Ubuntu, and then click Continue. Make sure to remember your username and password (you will need these to log into the system after your installation and for downloading/installing new software).
When the installation is complete it will prompt you to restart your virtual machine. Click Restart Now.
Note: If the machine doesn’t restart automatically go to machine -> reset, or you can try closing the window and selecting the option 'power off machine' if the virtual machine is unable to restart and then run it again.
Your virtual machine should now be up and running.
Now that Ubuntu is installed on your virtual machine and you have logged in with your Ubuntu username and password. Open the program called terminal (you can find it by clicking the window’s key or clover leaf key and type terminal into the search window). If you have deviated from this tutorial (for example, using a different virtualization technology like Windows Subsystem for Linux (WSL)) or to see the latest updated pre-requisite documentation please consult the nunaliit wiki documentaiton.
In the terminal, type the following commands:
sudo apt-get update
'sudo apt update' retrieves the latest list of software available via apt (Note: You will be prompted for your password when you type sudo for the first time. This is the same password as the one provided for your user account).
sudo apt upgrade
'sudo apt upgrade' downloads the latest software for your system. Next type Y to install the latest software updates.
sudo apt install -y openjdk-8-jre-headless
Paste each of the following commands into the terminal in the order they are presented below. Note that we want to use CouchDB 2 for Nunaliit, not the latest version 3 which is not (yet) supported. So we explicitly specify it and then have the package manager hold it at that version so it isn't inadvertently upgraded.
sudo apt update
sudo apt install apt-transport-https curl gnupg
curl https://couchdb.apache.org/repo/keys.asc | /usr/bin/gpg --dearmor | sudo /usr/bin/tee /usr/share/keyrings/couchdb-archive-keyring.gpg >/dev/null 2>&1
echo "deb [signed-by=/usr/share/keyrings/couchdb-archive-keyring.gpg] https://apache.jfrog.io/artifactory/couchdb-deb/ bionic main" | sudo tee /etc/apt/sources.list.d/couchdb.list >/dev/null
sudo apt update
sudo apt install -y couchdb=2.3.1~bionic
During the installation of couchdb you will be prompted for a few configurations. Select 'standalone', a bind address of '0.0.0.0', and lastly supply an admin password for couchdb (make sure to remember this password, since you will need it when you create Nunaliit atlases).
sudo apt-mark hold couchdb
Lastly you will need to restart couchdb by typing the following command into the terminal
sudo systemctl restart couchdb
sudo apt install -y imagemagick
sudo apt install -y ffmpeg ubuntu-restricted-extras
Nunaliit releases can be downloaded from the following website.
Pick the latest release (2.2.9-SNAPSHOT at the time of this instruction) and download the Nunaliit development kit (SDK) tar ball (e.g. nunaliit_2.2.9-SNAPSHOT_2019-09-30_44670e5.tar.gz).
Next you will need to uncompress/extract the tar-ball's files. You can either double click the file and uncompress/extract it to the desired directory using the graphical user interface, or you can uncompress/extract the files through the terminal (see command below).
tar –xzvf nunaliit_2.2.9-SNAPSHOT_2019-09-30_44670e5.tar.gz
You can create a new Nunaliit atlas using the following command in terminal. Note: make sure your current directory contains the extracted SDK sub-directory from the tar-ball:
./nunaliit_2.2.9-SNAPSHOT_2019-09-30_44670e5/bin/nunaliit create
Provide the information for configuring a new atlas.
-
Enter the name/location where the atlas will be created (keep the name lowercase and without spaces or special characters)
-
Note: many of the fields used for creating an atlas have default values (shown in [ ]), to accept these values simply hit enter/return.
-
Update the admin password to the new CouchDB admin password provided in Step 3.3.
-
See the screenshot of the create atlas configuration questions below;
Once the atlas has been created, it will need to be updated first and then run. Start by changing the directory to the new atlas sub-directory cd
, and then run the atlas update command as follows (Note: you may need to update the command below, if your copy of Nunaliit is located elsewhere):
../nunaliit_2.2.9-SNAPSHOT_2019-09-30_44670e5/bin/nunaliit update
and then type the following to run the atlas:
../nunaliit_2.2.9-SNAPSHOT_2019-09-30_44670e5/bin/nunaliit run
To see your atlas up and running go to the URL http://127.0.0.1:8080
(Note: replace the port 8080 with the port you provided in the initial create procedure if it’s different).
To stop the running atlas, type CTRL+C
in the terminal
This overview provides details on the structure of a Nunaliit atlas, with a focus on the files which are most relevant to a new user. For additional information on the atlas directory structure, please consult the nunaliit atlas builder wiki page.
Tree view of basic atlas:
atlas/
├── config
│ ├── install.properties
│ ├── mail.properties
│ ├── multimedia.properties
│ ├── nunaliit_manifest.json
│ ├── nunaliit.properties
│ ├── sensitive.properties
│ └── upload.properties
├── docs
│ ├── approved
│ ├── help.browsers
│ ├── help.demo
│ ├── module.demo
│ ├── navigation.demo
│ ├── org.nunaliit.email_template.daily_vetter
│ ├── org.nunaliit.email_template.form_email
│ ├── org.nunaliit.email_template.password_recovery
│ ├── org.nunaliit.email_template.password_reminder
│ ├── org.nunaliit.email_template.submission_approval
│ ├── org.nunaliit.email_template.submission_rejection
│ ├── org.nunaliit.email_template.upload
│ ├── org.nunaliit.email_template.user_creation
│ ├── org.nunaliit.user_agreement
│ ├── public
│ ├── schema.demo_comment
│ ├── schema.demo_doc
│ ├── schema.demo_media
├── extra
│ └── nunaliit.sh
├── dump
│ └── dump_2016-11-25_14:55:07
├── htdocs
│ ├── css
│ ├── favicon.ico
│ ├── index.html
│ ├── js
│ ├── nunaliit_custom.js
│ └── nunaliit_lang.en.js
├── logs
│ └── nunaliit.2016-11-25
├── media
└── site
└── views
As seen in the tree view above, every Nunaliit atlas contains a number of sub-directories which each relate to specific aspects of an atlas. For the purpose of simplicity, this overview will focus on the two directories which new atlas builders spend most of their time in; the docs and htdocs directories. The config, extra, dump, logs, media and site sub-directories will be ignored for the time being but future lessons will provide greater details on these excluded directories.
The Docs Directory: The docs directory contains documents which are pushed into the CouchDB database used by Nunaliit, and provide much of the structure of how data is viewed, edited and collected in an atlas. When you create a schema, module, layer, import profile, help document, or want to change the appearance of the navigation bar, this is the directory where you will need to work in.
The docs tree view below is of a newly created docs directory, which contains a variety of documents used to created the initial template of an atlas.
Docs Tree Structure:
docs/
├── approved
├── help.browsers
├── help.demo
├── module.demo
├── navigation.demo
├── org.nunaliit.email_template.daily_vetter
├── org.nunaliit.email_template.form_email
├── org.nunaliit.email_template.password_recovery
├── org.nunaliit.email_template.password_reminder
├── org.nunaliit.email_template.submission_approval
├── org.nunaliit.email_template.submission_rejection
├── org.nunaliit.email_template.upload
├── org.nunaliit.email_template.user_creation
├── org.nunaliit.user_agreement
├── public
├── schema.demo_comment
├── schema.demo_doc
└── schema.demo_media
In order to aid the understanding of what each of these files control in an atlas, the atlas screenshot shown below, describes which documents control which aspects of an atlas’s design.
The Htdocs Directory:
The htdocs directory contains the documents typically found in most web pages, including html, javascript and css files. When you need to edit the appearance of the atlas, add additional javascript libraries, or provide custom functionality, these changes typically occur in the htdocs directory.
Htdocs Tree Structure:
htdocs/
├── css
│ └── atlas.css
├── favicon.ico
├── index.html
├── js
│ └── index.js
├── nunaliit_custom.js
└── nunaliit_lang.en.js
Tips for naming atlas files: When creating new content in an atlas, consistency between directory names can prevent confusion (and in some cases errors) from arising. When creating a new files in the docs directory, the following format has proven useful.
<document_type>.<group_name>_<document_name>
e.g.
module.testatlas_about
module.testatlas_map
schema.testatlas_person
schema.testatlas_bank
navigation.testatlas
help.testatlas_map
layer.testatlas_banks
Although you’re not prevented from using your own directory structure, adopting something consistent in format will make identifying atlas content easier.
Tips for editing files: When editing atlas files, ensure that your text editor is set to utf-8 character encoding when editing and saving files. In some cases, text editors can add special characters or may leave temp files in the directory you’re editing in, so take care when selecting a text editor to edit files.
A schema provides guidelines for Nunaliit in the creation, editing and viewing of documents. They allow atlas builders a means to define what data to collect and how it should be viewed by the user. However, it’s important to note that how a schema is defined shouldn’t be viewed as hard set rules, but rather suggestions of the type of content expected in a document.
By default each atlas provides a demo document schema, a demo comment schema and a demo media schema. These schemas provide a new atlas builders a place to start testing out the functionality of the atlas right away. However, if you continue to use Nunaliit, you will likely need to build your own schema for a specific project. This tutorial provides the steps for creating your own schemas.
Typically when designing a schema, an atlas builder should first consider what data will be collected, and how to segment it into it’s core components. For example, an atlas which focuses on financial institutions, may contain data on banks, bank staff, bank clients, and transactions. This data can be simplified into three schemas: bank, person, and transaction. Notice that both bank staff and bank clients are merged into a single person schema, since their data will likely contain similar information. See below for an example of the fields needed for these three schemas.
To add a new schema to the atlas, you will need to run the command ‘nunaliit add-schema –id schemaid’ from the root directory of the atlas (similar to how you would run the update or run commands).
e.g. ../nunaliit2-couch-sdk-2.2.6/bin/nunaliit add-schema --id bank
This command will create a new schema, which is added to the atlas’ docs folder with the name ‘schema.groupname_schemaid’. Within the new schema’s directory, you will see the required files which define the structure of a schema. Initially these files contain the basic structure of the schema, and will need to be updated to represent the schema design mentioned above.
schema.testatlas_bank/
├── brief.txt
├── create.json
├── csvExport.json
├── definition.json
├── display.txt
├── export.json
├── form.txt
├── _id.txt
├── isRootSchema.json
├── label.txt
├── name.txt
├── nunaliit_schema.txt
├── nunaliit_type.txt
└── relatedSchemas.json
Updating the schema’s design can be performed by defining the structure of the schema in the definition.json file. Instructions on how to create definition.json files, including a complete list of field options, can be found on the atlas builder wiki pages.
Using the schema designs shown above, I’ve included three examples of the definition files for the bank, person and transaction schemas.
Bank Schema Definition:
{
"group": "testatlas"
,"id": "bank"
,"label": "Bank"
,"relatedSchemas": [ ]
,"initialLayers": [ "public" ]
,"attributes":[
{
"type":"string"
,"id":"name"
,"label":"Name"
,"includedInBrief":true
}
,{
"type":"string"
,"id":"branch_number"
,"label":"Branch #"
}
,{
"type":"string"
,"id":"address"
,"label":"Address"
,"textarea": true
}
,{
"type":"string"
,"id":"phone_number"
,"label":"Phone #"
}
,{
"type": "array"
,"elementType":"reference"
,"id":"employees"
,"label":"Employees"
}
,{
"type":"checkbox"
,"id":"atm"
,"label":"ATM location?"
}
]
}
Person Schema Definition:
{
"group": "testatlas"
,"id": "person"
,"label": "Person"
,"relatedSchemas": [ ]
,"initialLayers": [ ]
,"attributes":[
{
"type":"string"
,"id":"name"
,"label":"Name"
,"includedInBrief":true
}
,{
"type":"string"
,"id":"address"
,"label":"Address"
,"textarea": true
}
,{
"type":"string"
,"id":"phone_number"
,"label":"Phone #"
}
]
}
Transaction Schema Definition:
{
"group": "testatlas"
,"id": "transaction"
,"label": "Transaction"
,"relatedSchemas": [ ]
,"initialLayers": [ ]
,"attributes":[
{
"type": "reference"
,"id":"payer"
,"label":"Payer"
}
,{
"type": "reference"
,"id":"payee"
,"label":"Payee"
}
,{
"type":"string"
,"id":"amount"
,"label":"Amount"
}
,{
"type":"date"
,"id":"date"
,"label":"Date"
}
,{
"type":"string"
,"id":"details"
,"label":"Details"
,"textarea": true
}
]
}
e.g. ../nunaliit2-couch-sdk-2.2.6/bin/nunaliit update-schema --name testatlas_bankNote: The name of the schema is typically the groupname_schemaid. If you are unsure of the name of the schema, you should consult the name.txt file within the schema’s directory
e.g. ../nunaliit2-couch-sdk-2.2.6/bin/nunaliit update-schema --all
The update-schema tool updates each of the files in the schema directory with the newly defined schema’s structure. However these changes won’t be seen in the atlas until the atlas it’s self is updated.
e.g. ../nunaliit2-couch-sdk-2.2.6/bin/nunaliit update
Following the update of the atlas, you should now be able to use these schemas in your atlas. See a screenshot of the schema form view below.
Using the add-schema and update-schema tools allows for quick creation and updating of schema documents in Nunaliit. However, you are not limited to the use of these tools. You can edit the files in the schema directory, allowing an atlas builder to customize your schema’s design. Note: If you decided to customize a schema, you will likely want to delete your definition.json file from the schema directory to prevent your custom changes being overwritten accidentally with the nunaliit update-schema --all
.
Nunaliit schemas that have fields of type ‘reference’ are able to use search functions, which refine which documents will be listed in a reference field when selected. Nunaliit has various pre-built search functions which are listed in the wiki.
Although a wide variety of pre-built search functions exist, occasionally you may need a custom search function. The following tutorial provides the steps to create a custom search function, which lists species documents, using the English name of the species, or the scientific name when the English name doesn’t exist.
- Add a custom search function to
htdocs/nunaliit_custom.js
.
// Custom Search Function class which lists species documents, using their English species name,
// or the species scientific name when the English name doesn't exist in the document.
var DialogFactorySearchBriefSpecies = $n2.Class($n2.couchDialogs.SearchBriefDialogFactory, {
atlasDesign: null,
initialize: function(opts_) {
var opts = $n2.extend({
atlasDesign: null,
showService: null,
dialogPrompt: 'Select Species'
},opts_);
$n2.couchDialogs.SearchBriefDialogFactory.prototype.initialize.call(this, opts);
this.atlasDesign = opts.atlasDesign;
},
getDocuments: function(opts_) {
var opts = $n2.extend({
onSuccess: function(docs) {},
onError: function(err) {}
},opts_);
// Get a collection of myatlas_species documents
this.atlasDesign.queryView({
viewName: 'nunaliit-schema',
startkey: 'myatlas_species',
endkey: 'myatlas_species',
include_docs: true,
onSuccess: function(rows) {
var docs = [];
for (var i = 0; i < rows.length; i += 1) {
var doc = rows[i].doc;
if (doc) {
docs.push(doc);
}
}
// Sort collection of species documents
docs.sort(function(d1,d2) {
var n1 = getSpeciesNameFromDoc(d1);
var n2 = getSpeciesNameFromDoc(d2);
if (n2 > n1) {
return -1;
} else if (n2 < n1) {
return 1;
} else {
return 0;
}
// Get the species name from a doc
// return the English name, or scientific name depending on availability.
function getSpeciesNameFromDoc(doc) {
var name = '';
if (doc && doc.myatlas_species) {
if (typeof doc.myatlas_species.english_name === 'string'
&& doc.myatlas_species.english_name.length > 0) {
name = doc.myatlas_species.english_name;
} else if (typeof doc.myatlas_species.scientific_name === 'string'
&& doc.myatlas_species.scientific_name.length > 0) {
name = doc.myatlas_species.scientific_name;
}
}
return name;
}
});
opts.onSuccess(docs);
},
onError: function(errorMsg) {
$n2.log('Unable to retrieve species documents: ' + errorMsg);
}
});
}
});
- Add search function to the Dialog Service in the
window.nunaliit_custom.configuration
function in thehtdocs/nunaliit_custom.js
:
window.nunaliit_custom.configuration = function(config, callback){
atlasDb = config.atlasDb;
atlasDesign = config.atlasDesign;
siteDesign = config.siteDesign;
// Dialog Service
if( config.directory.dialogService ){
var dialogService = config.directory.dialogService;
var dialogFactorySearchBriefSpecies = new DialogFactorySearchBriefSpecies({
atlasDesign: config.atlasDesign,
showService: config.directory.showService
});
dialogService.addFunctionToMap(
'myatlas_searchForSpecies',
dialogFactorySearchBriefSpecies.getDialogFunction()
);
...
}
...
}
- Use the custom function in your schema’s
definition.json
:
{
...
,{
"type": "array"
,"id": "species"
,"label": "Species"
,"elementType": "reference"
,"searchFunction": "myatlas_searchForSpecies"
,"includedInBrief": true
}
...
}
- Update schema using the updated schema definition. e.g.
../nunaliit/bin/nunaliit update-schema <schema-name>
Modules provide a means to organize the atlas into related content. They can contain a variety of data, and be presented in numerous formats including the use of dynamic maps, canvases, or if you prefer a simple html page containing traditional content like text and media.
The easiest method to create a new module, is to simply copy an existing module and update the id file. The following tutorial will provide instructions on how to accomplish this task.
module.demo/├── _id.txt
├── nunaliit_module
│ ├── display.json
│ ├── edit.json
│ ├── help.json
│ ├── introduction
│ │ ├── content
│ │ │ ├── en.html
│ │ │ ├── fr.html
│ │ │ └── nunaliit_type.txt
│ │ └── type.txt
│ ├── map.json
│ └── title.json
└── nunaliit_schema.txt
Within the docs directory of your atlas, first locate an existing module which you can copy. Note: If you’ve just created an atlas, a template for a module called module.demo should be available for copying.
To copy an existing module via the terminal, type the following command (Note: the Unix copy command ‘cp’ requires you to add the ‘-r’ recursive option in order to copy the directory and its contents):
cp -r <the name of the original module directory> <the name of the new module directory>
e.g. cp -r module.demo module.testatlas_map
Note: If you’re working in a GUI, you can alternatively copy the directory and paste it with a new name in the docs directory, as you would copy any directory on your OS.
Following the copying of the module, you should now have two modules with different directory names. However you still need to update the id of the new module, so that its different from the original module it was copied from.
Using a text editor, update the _id.txt file (located within the new module directory) with the new module id. The file will consist of a single line of text containing the id of the module. Using the example above the _id.txt file will contain the text ‘module.demo’ and will need to be updated to something else, such as ‘module.testatlas_map’.
The title of the module (seen in the thin bar beneath the larger navigation bar in the atlas), can easily be updated to any string of text. Within the module’s directory you will find the nunaliit_module directory, which will contains the title.json file that you will need to update.
module.demo/└── nunaliit_module
└── title.json
{
"nunaliit_type":"localized"
,"en":"Demo Atlas"
,"fr":"Atlas Demo"
}
As seen in the example above, the title.json file can contain multiple versions of the module title for different languages. Each version of the title is identified with the language code key (e.g. “en” for English) and corresponding value of the title in that language (e.g. “Demo Atlas”). If you wish to update the module’s title, simple update the title's text value for each required language used in your atlas (e.g. “en”:”Demo Atlas” could be updated to “en”:”Test Atlas Map”).
Note: If your atlas uses other languages in addition to the default English (en) and French (fr), simply include the language code and corresponding translated title, in the same manner as the en and fr examples above.
By default atlas documents are shown in the side panel using the classic display format. However, the Nunaliit framework can also display documents in a tile format which can be easily adjusted in an atlas with a single line of code.
The tile display can be applied to a single module, or applied to all modules throughout the atlas. To display documents in a tile display format for a specific module, you can edit the display.json file, located in the nunaliit_module directory. Within the display.json file you will need to add the key “type” and the value “tiled”.
module name (e.g. module.demo)/
└── nunaliit_module
└── display.json
{
"defaultSchemaName":"demo_doc",
"type":"tiled"
}
Alternatively, if you prefer the tile display over the classic format you can apply this preference to all modules in the atlas. This option can be applied by adding the displayFormat custom service option within the custom service portion of the window.nunaliit_custom.configuration function (located in the nunaliit_custom.js file).
atlas/
└── htdocs
└── nunaliit_custom.js
customService.setOption('displayFormat','tiled');
The initial map extent (bounding box) is set over Ottawa, Ontario, Canada. However it's unlikely that your project's study area will be centered over this region. To change this starting map extent for your module, you will need to update your module's map.json file.
module name (e.g. module.demo)/
└── nunaliit_module
└── map.json
Within the map.json file, you should see a key called coordinates with a property called initialBounds (see example below);
{
"coordinates": {
"initialBounds": [-75.72,45.41,-75.67,45.43]
,"autoInitialBounds": false
}
...
}
The initialBounds property represents the approximate starting area shown by the map, and is denoted using an array of x and y coordinates e.g. [x1, y1, x2, y2]. The x1 and y1 values represent the bottom left corner of the map, and the x2 and y2 values represent the upper right corner of the map.
To change your starting map area, simply replace the x and y values in the initialBounds property to your desired study area. Once this is complete, save the changes in the file, and run a nunaliit update
on your atlas. Your map module should now start with a new bounding box.
Note: In the above code block, the x and y coordinates are longitude and latitude decimal degree values (in that order). If your map is using a different reference system for displaying geographic data, you may need to use a different unit of measurement (e.g. metres).
1. Move the map in Nunaliit to your desired starting extent using the zoom and pan tools (i.e. drag the map to the choosen location and zoom in or out as required. 2. Move your cursor to the lower left corner of the map, and make a note of the coordinate position displayed in the bottom right corner of the map (e.g. -55.1234, 12.5678). 3. Now move your cursor to the top right corner of the map, and make a note of the coordinates in the same manner described in step 2. 4. You should now have two coordinate pairs. The coordinates from step 2 will replace your x1 and y1 values in the initialBounds property, and the coordinates from step 3 will replace your x2 and y2 values. If you're frequently updating your initial bounding box as new data is entered into you atlas, another option is to have the map the initial bounds to the full extent of your atlases data. This feature can be set by changing the property value of 'autoInitialBounds' from `false` to `true`. Note: This will override the bounding box set in the 'initialBounds' property.Zooming on the map can be performed in a number of ways; 1) double clicking on a map location will zoom in one level, 2) Clicking the '+' or '-' zoom buttons on the map, or 3) holding down 'shift' + clicking and dragging an area you wish to zoom to.
However another zoom option is enabling zooming when the mouse wheel is rotated. This functionality can be enabled in a module by editing the map.json file.
module name (e.g. module.demo)/
└── nunaliit_module
└── map.json
To enable wheel zoom on the map simply add the follwing line to your map.json file "enableWheelZoom": true
, save the changes, and perform a nunaliit update
on the atlas.
{
...
,"backgrounds": [
...
]
,"overlays": [
...
]
,"toggleClick": true
,"enableWheelZoom": true
}
Often atlases include a module solely for sharing information on a project, whether this is an overview of the project, details about partners, or contact information. Although you could simply add content directly to the module’s introduction content page(s) found within the module. Often a better option is to display this content in a wiki document. Wiki documents are easier to edit through the browser, and offer a simpler markup compared to HTML which has a built in help document in the atlas.
module name (e.g. module.demo)/
└── nunaliit_module
└── map.json or canvas.json
└── utilities.json
└── introduction
└── content
└── en.html (or other localized html documents, e.g. fr.html)
Note: If you don’t want to remove the map or canvas from the module, then you can skip this step.
If you have a document already created which you wish to display, you can skip this step. Otherwise you will need to create a document as you typically would, which contains a textarea field which is wiki-transformed.Note: Typically a new wiki-document schema is created which usually contains only two fields; 1) a title text field for giving a brief for the wiki-document, and 2) a textarea field which has wiki-transformed. See sample schema definition below;
{
"group": "myatlas"
,"id": "wikidoc"
,"label": "My Atlas Wiki Document"
,"relatedSchemas": []
,"initialLayers": []
,"attributes": [
{
"type": "string"
,"id": "title"
,"label": "Title"
,"includedInBrief": true
,"excludedFromDisplay": true
}
,{
"type": "localized"
,"id": "content"
,"label": "Content"
,"includedInBrief": false
,"textarea": true
,"wikiTransform": true
}
]
}
Sample module introduction content en.html document:
<div class="intro">
<div class="n2s_fullDisplay" nunaliit-document="123456789"></div><br/>
</div>
Note: If the same content is being shown in all languages, you should also remove all other localized html documents (e.g. fr.html) if they exist.
At this point the wiki document should be showing in your module, however if you wish to edit this document you will need to search for the document, view it, and then edit it in the typical editting manner. An easier approach would be to show an edit button directly below the wiki document so that logged-in users can easily edit the document.To add this edit functionality, an existing utilities.json document needs to be updated to include the "selectDocumentOnModuleIntroduction" utility as shown below. Copy the four lines relating to the utilityType (including the curly brackets), and replace the docId with the same wiki document id used in the step above.
Sample module utilities.json document:
[
{
"utilityType": "selectDocumentOnModuleIntroduction"
,"docId": "123456789"
}
]
Note: If no utilities.json file exists in the atlas, you can create a new utilities.json file in the nunaliit_module directory, and copy the entire sample code above into the file.
When accessing your atlas from the root domain (e.g. http://127.0.0.1:8080) it should load a default module. Initially this is set to the demo module but can be updated to a different module within the nunaliit_custom.js file, located in the htdocs directory.
atlas/└── htdocs
└── nunaliit_custom.js
Within the custom service configuration portion of the nunaliit_custom.js file, you should see a line of code which looks like the following;
customService.setOption('defaultModuleIdentifier','module.demo');
You will notice that the final parameter being passed to the setOption method, states the id of the demo module ‘module.demo’. To change the default module, simply replace the ‘module.demo’ id with the id of the new module you wish to have as the default.
Nunaliit atlases offers the language choices English and French for viewing the atlas content. However you can add new language choices to the atlas by creating a language definition object and adding it to the language service in the window function found in the htdocs/nunaliit_custom.js
.
The language definition object should include a language code and the name you want to show in the language picker widget drop down list.
Example:
window.nunaliit_custom.configuration = function(config, callback) {
var farsiDef = {
code: 'fa',
name: 'فارسی'
};
config.directory.languageService.addLanguage(farsiDef);
...
}
Nunaliit provides a method of loading custom scripts through the htdocs/nunaliit_custom.js
. To do this, pass a list of strings of script file locations to a $n2.scripts.loadCustomScripts
method. See example below.
Example:
window.nunaliit_custom.configuration = function(config, callback) {
...
callback();
};
// Custom scripts being loaded
$n2.scripts.loadCustomScripts([
'js/my_custom_script.js'
,'js/my_other_custom_script.js'
]);
})(jQuery,nunaliit2);
By default Nunaliit provides the spatial reference systems EPSG:4326 and EPSG: 900913, but additional srs can be added to Nunaliit, by including the proj4js definition to nunaliit_custom to allow transformations from one projection to another.
Steps to add a new projections:
- Go to https://spatialreference.org/ and search for coordinate system you wish to support in your atlas.
- In the search results, click on the EPSG code link for the coordate system you want to add.
- On the coordinate system's page you should see a link for the 'Proj4js format' (if available).
- Click on the 'Proj4js format' link and copy the cotents of the proj4js definition. e.g.
Proj4js.defs["EPSG:4269"] = "+proj=longlat +ellps=GRS80 +datum=NAD83 +no_defs";
- Open up htdoc/nunaliit_custom.js (see example below):
;(function($,$n2){
if( typeof(window.nunaliit_custom) === 'undefined' ) window.nunaliit_custom = {};
Proj4js.defs["EPSG:4269"] = "+proj=longlat +ellps=GRS80 +datum=NAD83 +no_defs";
- Lastly in your module, reference the EPSG code as the coordinate system you wish to use.
{
...
,"map":{
"coordinates": {
"initialBounds": [-75.72,45.41,-75.67,45.43]
,"autoInitialBounds": false
,"srsName":"EPSG:4269"
}
...
}
...
}
The atlas navigation bar provides an easy way to access atlas content. This content is commonly divided between different modules and hyperlinks to those modules are listed in the navigation bar.
This tutorial provides instructions on: 1) how to alter the navigation bar by editing a navigation document, 2) how to create new navigation documents, and 3) how to update the default navigation document used by an atlas.
navigation.demo/
├── _id.txt
├── nunaliit_navigation.json
└── nunaliit_schema.txt
The structure of the navigation document consists of three files; the _id.txt, nunaliit_navigation.json, and the nunaliit_schema.txt. The _id.txt file provides the id of the navigation document (e.g. navigation.demo). The nunaliit_schema.txt states the schema is of type ‘navigation’. The nunaliit_navigation.json contains the substance of the navigation document, organizing various links to different content.
By default Nunaliit provides a demo navigation document, navigation.demo. Although the navigation.demo can be updated and used for the atlas’ navigation bar, atlas builders often replace these demo files with atlas specific versions. This is generally a good practice since it makes the document easier to identify as being related to a specific atlas.To define a new navigation document, make a copy of the existing navigation.demo directory. For consistency, new navigation documents often receive the name navigation.atlas_name. For example if you created an atlas about World War II called wwii, than the navigation document would be called navigation.wwii.
To copy an existing module via the terminal, type the following command (Note: the Unix copy command ‘cp’ requires you to add the ‘-r’ recursive option in order to copy the directory and its contents):
cp -r the_name_of_the_original_module_directory the_name_of_the_new_module_directory e.g. cp -r navigation.demo navigation.wwii
Note: If you’re working in a GUI, you can alternatively copy the directory and paste it with a new name in the docs directory, as you would copy any directory on your OS.
Once the navigation document is copied and renamed, edit the _id.txt file so that the id matches the new directory name (e.g. navigation.wwii).
You now have a new navigation document with the same content as the navigation.demo.
Tip: You can make as many navigation documents as you want. Navigation documents which are not used by default, can still be viewed as a document in the side content panel. This is a useful way of creating a list of links to content which are still in development but aren’t ready for being included on the main navigation bar.
As previously mentioned the nunaliit_navigation.json file provides the structure for a list of links included in the navigation bar. Three attributes are used to define the structure of this json file: 1) the nunaliit_type which is given the value of “navigation”, 2) title which defines the label used for the atlas, and 3) an items attribute, which defines the links included in the navigation bar and how they’re organized. See the sample nunaliit_navigation.json file with corresponding sketch of the navigation bar below.{
"nunaliit_type":"navigation"
,"title":{
"nunaliit_type":"localized"
,"en":"Atlas Title"
}
,"items":[
{
"title": {
"nunaliit_type":"localized"
,"en":"a"
}
,"module": "module.a"
}
,{
"title": {
"nunaliit_type":"localized"
,"en":"b"
}
,"module": "module.b"
,"items":[
{
"title": {
"nunaliit_type":"localized"
,"en":"b1"
}
,"module": "module.b1"
}
,{
"title": {
"nunaliit_type":"localized"
,"en":"b2"
}
,"module": "module.b2"
,"items":[
{
"title": {
"nunaliit_type":"localized"
,"en":"b2a"
}
,"module": "module.b2a"
}
,{
"title": {
"nunaliit_type":"localized"
,"en":"b2b"
}
,"module": "module.b2b"
}
]
}
,{
"title": {
"nunaliit_type":"localized"
,"en":"b3"
}
,"module": "module.b3"
}
]
}
,{
"title": {
"nunaliit_type":"localized"
,"en":"c"
}
,"href": "./c.html"
}
]
}
Each link object, consists of a title attribute, and a link URL represented by either a ‘module’ attribute (used for hyper-links to other modules in an atlas) or a ‘href’ attribute (used for any website URL, including external content). In addition to these two attributes, an item in a list can have nested content (i.e. other links) by including an items array.
In the above sketch and corresponding nunaliit_navigation.json, there are three top level items a, b, and c, with the b link having three nested links, and once again the b2 having another nested list of two more links.
For example if I wanted to add a nested link under the c top level item to the nunaliit homepage, I would update the c item to look like the following;
...
,{
"title": {
"nunaliit_type":"localized"
,"en":"C"
}
,"href": "./c.html"
,"items":[
{
"title": {
"nunaliit_type":"localized"
,"en":"Nunaliit"
}
,"href": "http://nunaliit.org"
}
]
}
When an atlas is created, the default navigation document used by the atlas is the navigation.demo document. Although it is acceptable to use this navigation doc, you will likely want to change it as your atlas develops.
If you have another navigation document that you wish the atlas to use by default, you will need to change a single line of code in the nunaliit_custom.js file (located in the atlas’ htdocs directory). Within the window.nunaliit_custom.configuration function (typically located at the bottom of the nunaliit_custom.js file), you will see the following line;
customService.setOption('defaultNavigationIdentifier','navigation.demo');
To update the default navigation document, simply replace where it states navigation.demo with the replacement navigation document name.
Description: One of the simplest ways to give your atlas a custom look is by replacing the atlas title text with your own atlas logo. This tutorial provides instructions on this task, producing similar results as shown in the comparison example below.
Note: If you’re creating an image, the Nunaliit title bar’s height is 70px by default. If you want to maintain the dimensions of the original title bar, you should make the height of the logo no larger than that.
Replace the existing navigation document title attribute (located in the nunaliit_navigation.json file) with an empty string, “” (see example below). Note: This is required to prevent the title text from appearing over top of the image.{
"nunaliit_type": "navigation"
,"title": ””
,"items":[
. . .
]
}
The first style change is to assign the image as the background of the previous title text location, identified by the class ‘.nunaliit_title’ which is located within the ‘.nunaliit_atlas’ div. Copy the following example CSS code to the atlas.css file which defines the background as using the image file logo.png located in the images directory. Note: you will need to update the background URL to your file’s location in the htdocs directory and also update the dimensions to your desired image size.
.nunaliit_atlas .nunaliit_title {
background: url(../images/logo.png);
background-size: contain;
background-repeat: no-repeat;
width: 200px;
height: 50px;
}
At this point, your atlas should display your image in the atlas title location, once the atlas is updated. If its not appearing, make sure the location of the file is correct for the background URL.
The final step is to increase the size of the title link to match the size of the new title image using padding. This will result in the background image behaving similar to the text title when clicked by a user.Copy the following CSS code into the atlas.css file and update the padding to match the dimensions of your image. For example if your image is 180px wide and 60px high, you will need the following rule “padding: 30px 90px”.
.nunaliit_atlas .nunaliit_title_link {
/* 25px padding added to the top and bottom sides
100px padding added to the left and right sides */
padding: 25px 100px;
}
You have now successfully replaced your default title text with a logo.
Nunaliit provides a number of ways to create data in your atlas, which can be either spatial or non-spatial. To create a spatial document, the easiest method is through a map module. See the steps below.
- Make sure you're logged into the atlas
- Click the "Add or Edit a Map Feature" button in the top right corner of the map module.
- Five button should appear on the map window, which will allow you to pan the map (hand icon), make points (pencil icon), lines (line icon), polygons (polygon icon), or use a gazeteer service (Na icon) to create a spatial document. Note: if making a line or polygon, you need to double click on the map to complete the geometry.
- Click on either the point, line, polygon, or gazeteer button, and create a point, line, or polygon on the map.
- Once you've made the spatial feature on the map, you should now see an empty document form on the right side which can be populated with information. Note: The form which appears is the first schema defined in the module's edit.json file in the newDocumentSchemaNames property.
- After filling in the details for the document, click the 'Save' button and you should now have a new spatial document in the database.
- Click the "Create Document" link (may be called something else depending on the atlas).
- If more than one schema is listed in the module's edit.json newDocumentSchemaNames property, than a popup will appear where you can select from a list of schemas. Otherwise the only schema will automatically open up.
- Fill in the document form values for the schema document.
- Click the "Save" button.
- The Data Browser Tool can be accessed by going to the your tool page at
<youratlasurl>/tools/browse.html
. - On the Data Browser Tool page, click the "New Document" button (top right side of tool page).
- A popup window will appear, where you can select the schema you wish to make a document for.
- Fill in the document form values for the schema document.
- Click the "Save" button.
- Search and select the document in an existing map module in your atlas.
- Edit the document by clicking the "Edit" button at the bottom of the document.
- The geomerty tool buttons should automatically show on the map as they would if you clicked the "Add or Edit a Map Feature" button.
- Lastly create a geometry by adding a point, line or polygon using the steps listed in the Creating Spatial Documents section above.
Although the Nunaliit mapping framework is often used for collecting data, it can also import existing data-sets. By using an import profile JSON and GeoJSON documents can be imported into Nunaliit. This allows large data-sets to be quickly included into an atlas and can be re-synchronized when the data-set is updated and re-imported.
To import a JSON or GeoJSON file, you will need to create an import profile. An import profile provides the structure needed by Nunaliit to process external data-sets. It defines instructions on how and what type of data will be imported into an atlas, and what schema will be used for defining the structure of each newly created document that's imported.
To begin this process we should create a new import profile directory inside the docs directory which is where we will store the import profile. Typically a directory follows the naming conventions of the other directories in the docs directory. In this example we will be importing spatial data about cities and therefore will create a directory called import_profile.testatlas_cities (Note: you don’t need to follow this naming pattern, but it has proven useful for organizing one’s atlas work).
Within the import profile directory you will need to have two files. An _id.txt file for providing a unique id for the import profile, and a nunaliit_import_profile.json file used to define how data will be imported using this profile. See below for the structure of an import profile and examples of each file.
atlas/ └── docs
└── import_profile.testatlas_cities
├── _id.txt
└── nunaliit_import_profile.json
import_profile.testatlas_cities
{
"id":"cities",
"nunaliit_type":"import_profile",
"label":{"nunaliit_type":"localized","en":"Cities"},
"type":"geojson",
"options":{"idAttribute":"id"},
"operations":["copyAllAndFixNames(testatlas_city)"],
"schemaName":"testatlas_city",
"layerName":"public"
}
The nunaliit_import_profile.json file contains a number of items which need to be defined for an import profile to function correctly. Provided below is an explanation of each item;
- id: provides a unique id for the profile
- nunaliit_type: defines the type of document, which in this case is an “import_profile”
- label: the label field defines the name of the import profile shown by the drop down list in the Import tool window.
- type: defines the type of data being imported (either json or geojson).
- options: since the data being imported can be re-imported and the previous imported documents can be updated with changes (re-synchronized), you will need an idAttribute to compare changes between imports. In the example provided above, this field was given the value “id” but this could be any field in the data-set which has unique values. Note: if you’re data doesn’t contain a unique id field, you may need to add an id column to your table data and populated it with unique values prior to converting it to either JSON or GeoJSON format.
- operations: the operations field allows the user to define how the data should be imported. Available options include; CopyAll, CopyAllAndFixNames, assign, reference, and importReference. For greater detail on each, please consult the documentation (https://github.com/GCRC/nunaliit/wiki/Nunaliit-Documentation-for-Atlas-Builders#importing_data_profile)
- schemaName: the schema name is simply the name of the schema you want used when creating each of these documents. Note: make sure to spell the schema name correctly or the import profile will not be shown as a valid choice in the import tool window.
- layerName: the layer name field is an optional field, but can be used to associate a layer to each of the documents being imported.
Select the import profile you created from the drop down list and then copy and paste your JSON or GeoJSON data into the input text box (as shown above). Once successfully pasted, the data will need to be verified by clicking the Verify button.
Assuming no errors existed in the data-set, a series of new proposed documents or changes to existing documents will be listed below, which you can automatically process by clicking the “Proceed” button or by addressing each document individually. Note: make sure you’re logged in before you proceed with creating/making changes to existing documents with the import tool.
csv2json ( https://www.npmjs.com/package/csv2json):
If you’re able to perform this conversion, you can then easily convert your csv file into a json file using the command line program csv2json. The command to perform this conversion is simply csv2json [input csv file] [output json file]. Note: if you’re not a fan of working with the command line, there are also numerous websites which can also perform this csv to json conversion on your data.
QGIS ( http://www.qgis.org):
- When re-importing data, make sure to include all data you want to appear in the atlas (even if no changes occurred to previously imported data). If you only try to import new data and exclude existing data in the atlas, the import tool will assume you will want to delete previous data if it’s excluded from the import process.
- A copy of the imported data is stored in every document under the nunaliit_import key. If you make a change to the document which you don’t want overwritten with a future re-import, make sure to update the corresponding data in the nunaliit_import portion of the document.
- When importing large data-sets, it can be very taxing on the browser. Be prepared to be patient when verifying large quantities of data in the atlas.
- Make sure to match the column names of your imported data with the field names used in the schema. This will allow the imported data to be viewed/edited more easily in the atlas.
Although nunaliit documents can be manually modified on a document by document basis through the atlas interface. You can also modify a collection of documents using the data modification tool, using a JavaScript to automate the process.
- Login to the atlas
- Open the Data Modification tool page
<nunaliit-atlas-url>/tools/select.html
- Perform a Query on the data to get a collection of documents you wanted to change.
- Click the 'Transform' button.
- Select 'JavaScript Replace' as the drop down option, click 'OK' button.
- Add JavaScript to text window to transform your documents and click 'OK' button. (see examples below of transformation scripts).
// Steps to use script:
// 1. Update values for schemaName, oldFieldName, and newFieldName variables.
// 2. Replace default transform script with this updated script.
// 3. Run the script.
function(doc, onTransformedFn, onSkippedFn) {
var updateRequired = false;
var schemaName = 'schema_name';
var oldFieldName = 'old_field_name';
var newFieldName = 'new_field_name';
if (doc
&& Object.hasOwnProperty.call(doc, schemaName)) {
var schemaDoc = doc[schemaName];
if (Object.hasOwnProperty.call(schemaDoc, oldFieldName)
&& !Object.hasOwnProperty.call(schemaDoc, newFieldName)) {
schemaDoc[newFieldName] = schemaDoc[oldFieldName];
delete schemaDoc[oldFieldName];
updateRequired = true;
}
}
if( updateRequired ){
onTransformedFn();
} else {
onSkippedFn();
}
}
// Steps to use script:
// 1. Update values for oldSchemaName, and newSchemaName variables.
// 2. Replace default transform script with this updated script.
// 3. Run the script.
function(doc, onTransformedFn, onSkippedFn) {
var updateRequired = false;
var oldSchemaName = 'old_schema_name';
var newSchemaName = 'new_schema_name';
if (doc
&& Object.hasOwnProperty.call(doc, oldSchemaName)
&& !Object.hasOwnProperty.call(doc, newSchemaName)) {
doc[newSchemaName] = doc[oldSchemaName];
delete doc[oldSchemaName];
doc.nunaliit_schema = newSchemaName;
updateRequired = true;
}
if (updateRequired) {
onTransformedFn();
} else {
onSkippedFn();
}
}
// Steps to use script:
// 1. Update values for schemaName, and layerName variables.
// 2. Replace default transform script with this updated script.
// 3. Run the script.
function(doc, onTransformedFn, onSkippedFn) {
var updateRequired = false;
var schemaName = 'schema_name';
var layerName = 'new_layer';
if (doc
&& Object.hasOwnProperty.call(doc, schemaName)) {
if (!Object.hasOwnProperty.call(doc, 'nunaliit_layers')) {
doc.nunaliit_layers = [];
}
if (Array.isArray(doc.nunaliit_layers)
&& doc.nunaliit_layers.indexOf(layerName) < 0) {
doc.nunaliit_layers.push(layerName);
updateRequired = true;
}
}
if( updateRequired ){
onTransformedFn();
} else {
onSkippedFn();
}
}
// Example Data Modification Script which queries all myatlas_species documents
// and creates references to myatlas_species documents in myatlas_observation
// documents which have matching species name.
function(doc, onTransformedFn, onSkippedFn){
// Collect all species data
if (!window.speciesDocs) {
atlasDesign.queryView({
viewName: 'nunaliit-schema'
,startkey: 'myatlas_species'
,endkey: 'myatlas_species'
,include_docs: true
,onSuccess: function(rows) {
//Define empty array for storing relation data
window.speciesDocs = [];
//Push id values into arrays if this hasn't been encountered yet
for (var i = 0; i < rows.length; i += 1) {
var doc = rows[i].doc;
if (doc._id
&& doc.myatlas_species
&& doc.myatlas_species.name) {
var species_doc = {
"doc_id": doc._id,
"species_name": doc.myatlas_species.name
};
window.speciesDocs.push(species_doc);
}
}
if (window.speciesDocs) {
updateObservationDocs();
}
}
,onError: function(errorMsg) {
throw 'Error retrieving relation data';
}
});
}
if (window.speciesDocs) {
updateObservationDocs();
}
function updateObservationDocs() {
var updateRequired = false;
if (doc && doc.myatlas_observation && doc.myatlas_observation.species ) {
for (var i = 0; i < window.speciesDocs.length; i += 1) {
if (doc.myatlas_observation.species === window.speciesDocs[i].species_name) {
var species_reference = {
"nunaliit_type": "reference",
"doc": window.speciesDocs[i].doc_id
};
doc.myatlas_observation.species_ref = species_reference;
updateRequired = true;
}
}
}
if (updateRequired) {
onTransformedFn();
} else {
onSkippedFn();
}
}
}
Nunaliit’s show service provides the functionality to convert/update HTML content based on a show service class name and attribute(s) associated with a context HTML element. The following section will provide a simple example of how to use the show service in an HTML document. For more complete details on the show service, the official wiki has a full description of what the show service is and provides a full listing of existing show service classes.
To use a show service you need to associate a HTML element with a show service class (see available classes in wiki document linked above), provide the required Nunaliit attribute(s) to the element used by the specified show service, and in some cases you make need to manunally invoke the show service on that element.
The following example describes the process of inserting an existing media document into a module’s side panel. This is a simple example but demonstrates how show service classes are used, and how to add required attributes to a HTML element.- Search for the media document you want to show in the side panel, and open up the tree view for the document (note: you may need to be logged-in to view the tree view of the document). Inside the tree view, copy the document id from the
_id
key, and the media attachment name which can be found in thenunaliit_attachments.files.<filename>.originalName
. E.g. id = a84a476c370b1d51ab94c8ee7700ae0f, attachment file name = test.jpg. - Inside a module html page, edit it to include a new HTML element which has the show service media view class (
n2s_insertMediaView
), and the requirednunaliit-document
andnunaliit-attachment
attributes. Note: different attributes are needed for different show service classes, so check the wiki documentation for the requirements for each show service class.
<div class="n2s_insertMediaView" nunaliit-document="a84a476c370b1d51ab94c8ee7700ae0f" nunaliit-attachment="test.jpg"></div>
- Open/refresh the module page you’ve changed in a browser, and you should see a thumbnail of the media document you searched for in Step 1 which you can now click on to open the document. Note: In some cases (for example if the HTML content is being added with JavaScript), you will need to manually invoke the show service to update the element which is described in the wiki linked above using the show service function fixElementAndChildren.
Use Case: An atlas user is updating existing demo_place schema documents with location data when it's missing. To aid this user in this process, a list of demo_place schema documents needs to be displayed in a module side panel which lists all demo_place documents which don't currently have nunaliit geometries associated with them.
Steps to create a custom document list:
- Add custom list code to
htdocs/nunaliit_custom.js
, see handleDocumentQueryList function in code sample below for an example of custom document list code.
var atlasDesign;
function handleDocumentQueryList(m) {
// Handle custom document list show service 'demo_place_docs_without_geoms'
if (m.listType === 'demo_place_docs_without_geoms') {
var docs = [];
var docIds = [];
atlasDesign.queryView({
viewName: 'nunaliit-schema'
,keys: [m.listName, m.listName]
,include_docs: true
,reduce: false
,onSuccess: function(rows) {
var doc, schemaObj;
for (var i = 0; i < rows.length; i += 1) {
doc = rows[i].doc;
// Create list of demo_place documents which don't
// have nunaliit_geom data.
if (doc
&& doc[m.listName]
&& !Object.hasOwnProperty.call(doc,'nunaliit_geom')) {
if (docIds.indexOf(doc._id) < 0) {
docIds.push(doc._id);
docs.push(doc);
}
}
}
dispatchService.send(DH,{
type: 'documentListResults'
,listType: m.listType
,listName: m.listName
,docIds: docIds
,docs: docs
});
}
,onError: function(err) {
$n2.log('Unable to retrieve document list (' + m.listType + '/' + m.listName + ')',err);
}
});
}
}
- Register the custom document list code with the dispatcher inside the window.nunaliit_custom.configuration code.
window.nunaliit_custom.configuration = function(config, callback) {
atlasDesign = config.atlasDesign;
// Dispatch service
if (config.directory.dispatchService) {
dispatchService = config.directory.dispatchService;
dispatchService.register(DH,'documentListQuery',handleDocumentQueryList);
}
callback();
};
- Lastly you need to add the custom document list show service to a module's introduction html text. Notice that the nunaliit-list-type matches the list type defined in the handleDocumentQueryList example above.
<div class="n2s_insertDocumentList" nunaliit-list-type="demo_place_docs_without_geoms" nunaliit-list-name="demo_place"></div>
Nunaliit is bundled with an external slide show library called jr slide show which can be used to add a slide show/carousel to your atlas (e.g. the welcome page of an atlas often have a slide show). To use the jr slide show you need to perform the following steps to enable it in an atlas:
- Add the following reference to the external library to the bottom of the
htdocs/index.html
.<script type="text/javascript" src="js-external/js/jr_slideshow.js"></script>
- Register the slide show to the start dispatch event in the window.nunaliit_custom.configuration function, often found near the bottom of the
htdocs/nunaliit_custom.js
file .
window.nunaliit_custom.configuration = function(config, callback) {
...
if (config.directory.dispatchService) {
var dispatchService = config.directory.dispatchService;
dispatchService.register(‘demo’,'start',function(m) {
if (typeof window.slideSwitch === 'function') {
window.slideSwitch();
setInterval(window.slideSwitch, 10000);
}
});
}
...
callback();
}
- Add the following jr slide show html code to the module’s introduction html document. Note: You will need to update the example below for each slide with the correct reference for each image file, slide title, and slide description.
<div id="jr_slideshow">
<div class="jr_slideshow_container" style="opacity: 0;">
<div class="jr_slideshow_slide" style="background-image:url('./images/slide1.jpg')">
<div class="jr_slideshow_slidetext">
<h1>Slide 1 title</h1>
<p>Slide 1 description</p>
</div>
</div>
</div>
<div class="jr_slideshow_container" style="opacity: 0;">
<div class="jr_slideshow_slide" style="background-image:url('./images/slide2.jpg')">
<div class="jr_slideshow_slidetext">
<h1>Slide 2 title</h1>
<p>Slide 2 description</p>
</div>
</div>
</div>
<div class="active jr_slideshow_container" style="opacity: 1;">
<div class="jr_slideshow_slide" style="background-image:url('./images/slide3.jpg')">
<div class="jr_slideshow_slidetext">
<h1>Slide 3 title</h1>
<p>Slide 3 description</p>
</div>
</div>
</div>
</div>
- Lastly the following style rules should be added to the
htdocs/atlas.css
file for a full width slide show in an atlas. Note: Make sure to update the module div class name (<module-name>
) to match the module name that the jr slide show is being installed on.
/* -------------------- Style Rules for JR Slide Show ------------------ */
.nunaliit_module_module_<module-name> #jr_slideshow {
height: 800px;
}
.nunaliit_module_module_<module-name> #jr_slideshow .jr_slideshow_container:not(.active):not(.last-active) {
top:unset;
right:unset;
background-color: unset;
background-image: unset;
border:unset;
padding:0px;
position:absolute;
z-index: 0;
display: block;
}
.nunaliit_module_module_<module-name> #jr_slideshow .jr_slideshow_container .jr_slideshow_slide {
position: absolute;
width: 100%;
height: 600px;
background-color: #000000;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
.nunaliit_module_module_<module-name> #jr_slideshow .jr_slideshow_slide .jr_slideshow_slidetext {
position: absolute;
top: 20px;
right: 20px;
width: 20%;
min-width: 300px;
background-color: rgba(255,255,255,0.8);
padding: 20px;
border-radius: 2px;
z-index:2;
}
.nunaliit_module_module_<module-name> #jr_slideshow .jr_slideshow_slide .jr_slideshow_slidetext p {
padding-top: 7px;
}
.nunaliit_module_module_<module-name> #main {
padding: 15px 40px 15px 40px;
}
.nunaliit_module_module_<module-name> .n2_content_text,
.nunaliit_module_module_<module-name> .n2ModuleIntro {
padding:0px !important;
}
.nunaliit_module_module_<module-name> div#overview_content {
margin: 10px;
}
- (Optional) If you want the slide show to fill the full width of the screen (not just the side panel), you will need to make sure the module doesn’t use either a map or canvas by deleting either the map.json or canvas.json file from that module.
Nunaliit has a lot of built in code, but often what is needed is a variation of what is offered by generic Nunaliit. This portion of the tutorial will walk you through the process of creating a custom filter or a custom widget. The following custom filter tutorial demonstrates the process of creating a custom filter based on year values stored in demo_project documents. The tutorial demonstates how to extend an existing Nunaliit class, handle model events, and use that model filter with a selection widget. A client has an atlas which contains demo_project documents which contains a field called year, and the year field contains a string value representing the year. The client has requested a model driven filter so that they can filter their demo_project documents, based on year.
Thankfully Nunaliit has much of this logic developed with it's $n2.modelFilter.SelectableDocumentFilter class, however it will need to adapted for the use case of filtering demo_project documents by year values. Therefore the first step is to extend this existing Nunaliit filter class and customise it for this scenario. In the sample code below, I've included a modified nunaliit_custom.js file (located in the htdocs directory). Although much of the code should look familiar to you, portions of it are unique to this use case, including;
- Near the top of the code block below, you should see a new custom filter class called 'CustomFilterByYear'. This includes all of the logic for the extend $n2.modelFilter.SelectableDocumentFilter class.
- After the CustomFilterByYear class code block, there is a new handleModelEvents function. This function contains the logic required for handling model events for specific model types. In our case, if the modelType 'filterProjectsByYear' is encountered than the a new CustomFilterByYear object is instantiated.
- The last piece of new code, is near the bottom of the code block, in the window.nunaliit_custom.configuration function. Inside this function, you will see
dispatchService.register('demo', 'modelCreate', handleModelEvents);
which registers the handleModelEvents function when modelCreate events occur, so that our handleModelEvents function can process modelCreate events.
;(function($,$n2) {
if (typeof(window.nunaliit_custom) === 'undefined') {
window.nunaliit_custom = {};
}
// Custom year filter which extends the Nunaliit
// $n2.modelFilter.SelectableDocumentFilter Class.
//
// This class creates a custom model filter by taking in a collection of
// documents from a source model that contains documents with the
// following structure;
// {
// ...
// demo_project: {
// name: <name-of-project>,
// year: <year-of-project>
// }
// ...
// }
//
var CustomFilterByYear = $n2.Class('CustomFilterByYear', $n2.modelFilter.SelectableDocumentFilter, {
dispatchService: null,
initialize: function(opts_) {
var opts = $n2.extend({
modelId: null
,sourceModelId: null
,dispatchService: null
},opts_);
this.disabled = opts.dispatchService;
$n2.modelFilter.SelectableDocumentFilter.prototype.initialize.call(this,opts);
$n2.log('CustomFilterByYear', this);
},
/*
* Computes the available choices used by filter.
* Specifically it creates a list of available filter choices based on
* the year key in the project object contained in each document.
* @param {array} docs - An array of nunaliit documents
* @param callbackFn - Callback function
*/
_computeAvailableChoicesFromDocs: function(docs, callbackFn) {
var uniqueYears;
var years = {};
var availableChoices = [];
// Get a collection of unique year values.
docs.forEach(function(doc) {
if (doc && doc.demo_project
&& doc.demo_project.year) {
years[doc.demo_project.year] = 0;
}
});
// Generate availableChoices array based on the collection of years
uniqueYears = Object.keys(years);
uniqueYears.forEach(function(year) {
availableChoices.push({
id: year,
label: year
});
});
// Sort the availableChoices array
availableChoices.sort(function(a,b) {
if (a.label < b.label) {
return -1;
}
if (a.label > b.label) {
return 1;
}
return 0;
});
this.currentCallback = callbackFn;
callbackFn(availableChoices);
return null;
},
// Filter documents in source model based on the selected choice.
_isDocVisible: function(doc, selectedChoiceIdMap, allSelected) {
var docYear;
if (doc && doc.demo_project) {
// If the filter option allSelected is select in the widget
// then all documents in the source model pass through the
// filter. Otherwise each document needs to be checked to see
// if its filters based on its contents.
if (allSelected) {
return true;
} else if (doc.demo_project.year) {
docYear = doc.demo_project.year;
if (selectedChoiceIdMap[docYear]) {
return true;
}
}
return false;
}
// All other docs in the source model, let them through the filter
return true;
}
});
// Handle create model events for different model types
function handleModelEvents(m, addr, dispatcher) {
var options, objKeys, value;
if (m.type === 'modelCreate') {
if (m.modelType === 'filterProjectsByYear') {
options = {};
if (m.modelOptions) {
objKeys = Object.keys(m.modelOptions);
objKeys.forEach(function(key) {
value = m.modelOptions[key];
options[key] = value;
});
}
options.modelId = m.modelId;
if (m && m.config) {
options.atlasDesign = m.config.atlasDesign;
if (m.config.directory) {
options.dispatchService = m.config.directory.dispatchService;
}
}
m.model = new CustomFilterByYear(options);
m.created = true;
}
}
}
// This is a custom function that can be installed and give opportunity
// for an atlas to configure certain components before modules are displayed
window.nunaliit_custom.configuration = function(config, callback) {
var dispatchService;
config.directory.showService.options.preprocessDocument = function(doc) {
return doc;
};
// Dispatch service
if (config.directory.dispatchService) {
dispatchService = config.directory.dispatchService;
// Handler called when atlas starts
dispatchService.register('demo', 'start', function(m) {});
// Handler called when the module content is loaded
dispatchService.register('demo', 'loadedModuleContent', function(m) {});
// Handler called when model events occur
dispatchService.register('demo', 'modelCreate', handleModelEvents);
}
callback();
};
})(jQuery,nunaliit2);
Now that the custom filter code has been added to nunaliit_custom.js, the next step is to use the custom filter in your module. In this case, both models.json code and widgets.json in a module need to be updated/added. Examples of both are shown below. Notice that in the models.json file, the model type used by the filter matches the model type in the handleModelEvents function above. Also notice that the source model id used by the widget matches the model id in the models.json.
models.json:
[
{
"_comment": "Load documents"
,"modelType":"couchDb"
,"modelId": "couchDb"
,"selectors": [
{
"type":"couchDbSchema"
,"options": {
"schemaName": "demo_project"
}
,"name": {
"nunaliit_type": "localized"
,"en": "Demo Projects"
}
,"visibility": true
}
]
},
{
"modelType": "filterProjectsByYear"
,"modelId": "filterByYear"
,"sourceModelId": "couchDb"
}
]
widgets.json:
[
{
"widgetType": "singleFilterSelectionWidget",
"containerClass": "nunaliit_module_title",
"sourceModelId": "filterByYear",
"noChoiceLabel": "Select a Year",
"allChoicesLabel": "All Years"
}
]
At this point you should have a functioning custom filter working in a nunaliit module. If you run into problems, common issues include; the model used by your filter isn't set up properly (e.g. model selection uses a schema name which is incorrect), or the modelId and modelType values don't match between the various parts of your code, or there is a error in your custom code.
Nunaliit offers a large number of widgets which are frequently asked by atlas builders (see wiki for examples), but occasionally a custom widget is needed for functionality not already built into Nunaliit. This tutorial will walk you through the process of creating a simple button widget, which both sends and listens for unique Nunaliit dispatcher events.
- Create a new widget class containing the custom code for the widget. An example of this can be seen in the BtnWidget sample below.
- The new custom widget needs to be handled by for checking availability and displaying the widget if it’s available.
// Simple widget class that demostrates the basic structure of a widget which
// uses custom dispatch event.
//
// The widget creates a button, which when clicked sends a dispatch event that
// when handled displays an alert window on the screen.
var BtnWidget = $n2.Class('BtnWidget', {
containerClass: null,
dispatchService: null,
initialize: function(opts_){
var opts = $n2.extend({
config: null
,options: null
,widgetOptions: null
},opts_);
var _this = this;
if (opts.widgetOptions && opts.widgetOptions.containerClass) {
this.containerClass = "." + opts.widgetOptions.containerClass;
} else {
$n2.logError("containerClass must be defined for button widget");
}
if (opts.config) {
if (opts.config.directory) {
this.dispatchService = opts.config.directory.dispatchService;
};
};
// Create Button and set initial state
this._createButton();
if (this.dispatchService) {
// Register with dispatch service
var f = function(m, addr, dispatcher){
_this._handle(m, addr, dispatcher);
};
this.dispatchService.register(DH, 'buttonWidgetClicked', f);
// Toggle display type (grid or list view)
$('.btn_widget').on('click', function() {
_this.dispatchService.synchronousCall(DH,{
type: 'buttonWidgetClicked'
});
});
};
$n2.log("BtnWidget: ", this);
},
// Add a button element to the container class
_createButton: function() {
// Remove button if it already exists
$('.btn_widget').remove();
$('<button>')
.addClass('btn_widget')
.text('Button Widget')
.appendTo(this.containerClass);
},
// Display an alert window once a button widget click event is handled
// by the dispatcher.
_showAlert: function(m) {
alert('Button widget Was clicked!');
},
// Dispatch event handler function
_handle: function(m){
// When 'buttonWidgetClicked' events occur, call _showAlert method.
if( 'buttonWidgetClicked' === m.type ){
this._showAlert(m);
};
}
});
function handleWidgetIsTypeAvailable(m){
if (m.widgetType === 'button_widget') {
m.isAvailable = true;
}
};
function handleWidgetDisplay(m){
if (m.widgetType === 'button_widget') {
new BtnWidget({
config: m.config,
widgetOptions: m.widgetOptions
});
}
};
window.nunaliit_custom.configuration = function(config, callback) {
if (config.directory.dispatchService) {
dispatchService = config.directory.dispatchService;
dispatchService.register(DH,'widgetIsTypeAvailable',handleWidgetIsTypeAvailable);
dispatchService.register(DH,'widgetDisplay',handleWidgetDisplay);
}
callback();
};
- Add the custom button widget to a module’s widgets.json. Note: that the widgetType matches the widget type defined in the handleWidgetIsAvailable and handleWidgetDisplay functions defined in the
htdocs/nunaliit_custom.js
.
[
...
,{
"widgetType": "button_widget"
,"containerClass": "nunaliit_module_title"
}
]
- The last step is updating the atlas, and refreshing the browser. The new custom widget should now be seen in the module you installed it on, and when clicked an alert window should appear.
When developing/fixing code for Nunaliit, you will often need to debug the code.
Accessing the debug version of Nunaliit:
The majority of Nunaliit is written in JavaScript, and consequently you will often need to debug this code through the browser. By default, Nunaliit is loaded in the browser as a single merged file containing the entire framework. This merging of code, offers performance improvements, but also makes debugging harder.
To load a non-merged version of Nunaliit, you can update the script tag which loads Nunaliit to the debug version in the index.html located in the htdocs directory.
Script Tag of Non-Debug Nunaliit:
<script type="text/javascript" src="nunaliit2/nunaliit2.js"></script>
Script Tag with Debug Nunaliit:
<script type="text/javascript" src="nunaliit2/nunaliit2-debug.js"></script>
Following this update to the index.html, you should now see a list of all of the nunaliit modules inside the nunaliit2 folder in your browser’s debug tool (Source tab in Chrome, Debugger tab in Firefox).
Logging the dispatcher in the browser:
One of the power features of Nunaliit, is it's dispatcher system, which aids in the communication between different parts of the framework. If you want to log the dispatcher events, this can be done through the Chrome browser, by doing the following;
- Inspect the nunaliit atlas to open up the debug tools
- Under the Console tab, you should see an
nunaliit configuration
object being console logged to the console. - Inside
nunaliit configuration
->directory
->dispatchService
which contains options for logging which are set to false. - Update the dispatchService options;
logging
andloggingIncludesMessage
both to true and the console should now output dispatch messages to the console, for events its processing.
Accessing the Model Browser Widget as an Nunaliit Advance User:
Nunaliit provides a model browser widget which allows the user to inspect the module's models through the browser. This can be a helpful tool when reviewing the changes to the model. To access this widget you will need to be an advance user. Advance users can be defined in the couchDb user documents, or you can simple add the css class nunaliit_user_advanced
to the body element. If module uses models, a models
link should apper in the footer (bottom right corner of screen), which when clicked will open up the model browser widget.