Last update on Saturday, January 12th 2019

Marker Selection and Property Modification in an Augmented Reality Ionic App using Wikitude

Having some Markers is cool, but we need to be able to modify them!
In this tutorial we dive into some basic modifications by changing the content and associating a new image when tapping on a Marker.

We will skip some beginner code so go back to the previous tutorial otherwise you will be lost. The experiment's code is also available there.

Our final goal:

Ionic 3 mobile app development augmented reality wikitude select marker

Our POIs

We will use multiple POIs, those data will be located in the meeting-points.js file so we include it in the index.html file:

<body>
  <script src="js/meeting-points.js"></script>
  <script src="js/marker.js"></script>
  <script type="text/javascript" src="js/multi-meeting.js"></script>
</body>

Here is the meeting-point.ts content:

meetingPoints = [
  {
    id: 1,
    shortDescription: "The Angular meeting is here",
    longDescription:
      "Loads of things about Components, Services and much more!",
    name: "Angular Meeting",
    icon: "angular.png"
  },
  {
    id: 2,
    shortDescription: "The Ionic meeting is here",
    longDescription: "Loads of things about Pages, Cordova and much more!",
    name: "Ionic Meeting",
    icon: "ionic.png"
  },
  {
    id: 3,
    shortDescription: "The JavaScriptTuts meeting is here",
    longDescription:
      "Loads of things about Ionic, Augemented Reality and much more!",
    name: "JavaScriptTuts Meeting",
    icon: "javascripttuts.png"
  },
  {
    id: 4,
    shortDescription: "The TypeScript meeting is here",
    longDescription: "Loads of things about Classes, Interfaces and much more!",
    name: "TypeScript Meeting",
    icon: "ts.jpeg"
  }
];

Typically this data should be on a remote server, I didn't use a JSON file because I wanted to avoid using JQuery while keeping it simple.

Here we have a different shortDescription used when the marker is not selected, a longDescription when the marker is selected and we want to have more details about the meeting, a name and the icon to use.

We will have some new content in our World, starting the multi-meeting.js as follow:

var World = {

  markerDrawableSelected: null,

  markerList: [],

  currentMarker: null,
  .
  .
  .

  onScreenClick: function onScreenClickFn() {
    if (World.currentMarker) {
      World.currentMarker.setDeselected(World.currentMarker);
    }
  }
}

AR.context.onScreenClick = World.onScreenClick;

Our World will use a Resource stocked in markerDrawableSelected , it will be the same for each marker.
An empty markerList is initialized and the currentMarker property is set to null for now.
The onScreenClick hook is used to deselect the currentMarker when the user taps somewhere else on the screen.

The locationChanged hook doesn't change much:

locationChanged: function locationChangedFn(lat, lon) {
  if (!World.initiallyLoadedData) {
    World.requestDataFromLocal(lat, lon);
    World.initiallyLoadedData = true;
  }
}

For now we only create three POIs:

requestDataFromLocal: function requestDataFromLocalFn(
  centerPointLatitude,
  centerPointLongitude
) {
  var poisToCreate = 3;
  var poiData = [];
  for (var i = 0; i < poisToCreate; i++) {
    poiData.push({
      id: meetingPoints[i].id,
      longitude: centerPointLongitude + (Math.random() / 5 - 0.1),
      latitude: centerPointLatitude + (Math.random() / 5 - 0.1),
      shortDescription: meetingPoints[i].shortDescription,
      longDescription: meetingPoints[i].longDescription,
      altitude: AR.CONST.UNKNOWN_ALTITUDE,
      name: meetingPoints[i].name,
      icon: meetingPoints[i].icon
    });
  }
  World.loadPoisFromJsData(poiData);
}

Most of the data comes from the meetingPoints variable initialized in the meeting-points.js file.

Note the AR.CONST.UNKNOWN_ALTITUDE constant, which is pretty handy if you don't know your altitude.

Once the poiData ready, we call the loadPoisFromJsData method:

loadPoisFromJsData: function loadPoisFromJsDataFn(poiData) {
  World.markerList = [];

  World.markerDrawableSelected = new AR.ImageResource(
    "assets/marker-selected.png"
  );
  World.markerDrawableDirectionIndicator = new AR.ImageResource(
    "assets/indicator.png"
  );

  for (
    var currentPlaceNr = 0;
    currentPlaceNr < poiData.length;
    currentPlaceNr++
  ) {
    World.markerDrawableIdle = new AR.ImageResource(
      "assets/" + poiData[currentPlaceNr].icon
    );

    var singlePoi = {
      id: poiData[currentPlaceNr].id,
      latitude: parseFloat(poiData[currentPlaceNr].latitude),
      longitude: parseFloat(poiData[currentPlaceNr].longitude),
      altitude: parseFloat(poiData[currentPlaceNr].altitude),
      title: poiData[currentPlaceNr].name,
      shortDescription: poiData[currentPlaceNr].shortDescription,
      longDescription: poiData[currentPlaceNr].longDescription
    };

    World.markerList.push(new Marker(singlePoi));
  }
}

Starting by creating a list of markers, then looping on the poiData.
A unique markerDrawableIdle is created with the meeting icon and all the rest is pretty standard (alt, lat, etc).

The marker is created and pushed in the markerList.

The following method is more interesting. When a marker is selected:

onMarkerSelected: function onMarkerSelectedFn(marker) {
  if (World.currentMarker) {
    if (World.currentMarker.poiData.id == marker.poiData.id) {
      return;
    }
    World.currentMarker.setDeselected(World.currentMarker);
  }

  marker.setSelected(marker);
  World.currentMarker = marker;
}

If there is already a marker selected and it's basically the same marker, we do nothing.
Otherwise, we deselect the current marker. All of this is done on the World's side because it's the one that is aware of every markers' state.

Finally we select the marker and set it as the current marker.

Moving on to the Marker Class now!

The Marker

Some changes there:

class Marker {

  constructor (poiData) {
    this.isSelected = false;

    this.markerDrawableIdle = new AR.ImageDrawable(World.markerDrawableIdle, 2.5, {
        zOrder: 0,
        onClick: Marker.prototype.getOnClickTrigger(this)
    });

    this.markerDrawableSelected = new AR.ImageDrawable(World.markerDrawableSelected, 2.5, {
        zOrder: 0,
        enabled: false,
        onClick: Marker.prototype.getOnClickTrigger(this)
    });
    .
    .
    .
  }
}

The isSelected flag will help us later, by default every markers are not selected.

The markerDrawableSelected is not enabled by default, later when a marker will be selected we will change this property.

The onClick method will be returned by another method getOnClickTrigger, which is as follow:

  getOnClickTrigger (marker) {
      return function() {

          if (marker.isSelected) {
              marker.setDeselected(marker);
          } else {
              marker.setSelected(marker);
              try {
                  World.onMarkerSelected(marker);
              } catch (err) {
                  alert(err);
              }
          }
          return true;
      };
  };

If the marker is selected it will use the setDeselected method, otherwise it will use the setSelected method AND tell the world that a new marker has been selected.

The setSelected and setDeselected methods are where we do the visual modifications.

After all the work we have done, everything will be finalized in those two methods that only contain four lines of code each:

  setSelected (marker) {
      marker.isSelected = true;
      marker.descriptionLabel.text = marker.poiData.longDescription;

      marker.markerDrawableIdle.enabled = false;
      marker.markerDrawableSelected.enabled = true;
  };

  setDeselected (marker) {
      marker.isSelected = false;
      marker.descriptionLabel.text = marker.poiData.shortDescription;

      marker.markerDrawableIdle.enabled = true;
      marker.markerDrawableSelected.enabled = false;
  };

Starting by toggling the isSelected property.
The marker.descriptionLabel.text property is accessed and will either become a long description or a short description using the poiData property.
Finally the enable property of the Drawable Class allows us to switch between the Idle Drawable and the Selected Drawable.

Conclusion

Compared to the previous tutorials, this one is more complicated.
There are many hooks to handle there with the communication between a Marker and the World.
The more features a World contains, the more complex it becomes. You can already imagine that adding some information from authentication, social media or sounds will make your world more interesting and more complex to create and debug.
Right now, the switch between the Idle and Selected Drawable is instantaneous, in the next tutorial we will have a look at animations to make it smoother.
Until next time.

Integrating a Labeled Marker in an Augmented Reality Ionic App using Wikitude

Learn how to
integrate a mark...
...

Action Range and spooky Sounds in an AR Ionic app with Wikitude

Learn how to use
Wikitude's Actio...
...

Augmented Reality in Ionic 3 using Wikitude

Learn how to
integrate Wikitu...
...

Stay up to date


Join over 4000 other developers who already receive my tutorials and their source code out of the oven with other free JavaScript courses and an Angular cheatsheet!
Designed by Jaqsdesign