Welcome to Blogs @ Andrew Qu
Blog Index
All blogs
Search results

Plane Spotters Web Page - Demo Using DITA Publishing Standards


In this post, I am trying something new with DITA (Darwin Information Typing Architecture) standards. This simplistically illustrates one way of publishing web pages using DITA. The project consists an Eclipse plugin for UI, a web server for web services, content management, DITA-OT for web page publishing.

Project Architecture

The following sketch shows the project architecture:

Eclipse plugin is the authoring frontend that provides UI for viewing/editing data where the plane spotter can enter data of a new sighting of planes. Information on previous sightings may also be viewed or searched. The plugin talks to the cloud server using web services to store/retrieve data.

Browser view is the publishing end of the project where plane sightings are published to plane spotting enthusiasts and the general public.

The cloud server consists of data management, web services, DITA-OT and the web server that serves web pages. The web services module is the key component of the project that coordinates all activities on the server. It sends data to Eclipse plugin on request. When new or modified data is received from Eclipse plugin, the data is stored / refreshed, DITA files generated or regenerated, DITA-OT is called to generate / regenerate static web pages. The web sever handles web service / web page requests.

Project Data Modelling

The following is the (simplified) data model for our project.

idprimary keyautomatically increment int
spotterforeign keypoints to the Spotter object
spotter_idprimary keyAuto increment id
namestringName of the spotter
emailstringEmail address of the spotter
locationforeign keypoints to the Location object
location_idprimary keyAuto increment id
namestringName of the location
latitudefloatLocation's latitude
longitudefloatLocation's longitude
timedate timeTime of plane sighting
nvpintegerNumber of vapour trails
aircraftforeign keypoints to the Aircraft object
idprimary keyAuto increment aircraft type id
typechoicesType of aricraft from a selection of aircraft types
engineforeign keypoints to the Engine object
typestringType of engine from a selection
numberintegerNumber of engines
positionsstringPositions of engines
noise_descstringDescription of engine noise/td>
noise_audiostringList of sound recording files: wav1;wav2;...
sizeforeign keypoints to the Size object
lengthstringAircraft length
wingspanstringAircraf wing span
tailheightstringHeight of the aircraft tail
wingforeign keypoints to the Wing object
numberintegerNumber of wings
positionstringChoice of wing positions:Above,Below,Middle
sweptintegerSwept rearwards(degrees)
markingsstringDescription of any markings on the plane
photosstringname of image files
The Aircraft class
class Aircraft(models.Model) :
   id = models.AutoField(primary_key=True)
   type = models.CharField(max_length=25, choices=GetTypeChoice('aircraft'), default="unknown")
   engine = models.ForeignKey('Engine', on_delete=models.PROTECT, default=1)
   size = models.ForeignKey('Size', on_delete=models.PROTECT)
   wing = models.ForeignKey('Wing', on_delete=models.PROTECT)

   def __unicode__(self):
      return self.type + " " + str(self.wing) + " " + str(self.size)

The GetTypeChoice function
def GetTypeChoice(type_type) :
   fpath = os.path.join(APP_ROOT, "static", type_type + ".cat")
      fcat = open(fpath)
      cat = ()
      i = 0
      for line in fcat :
         line = line.strip()
         if len(line) > 0 and line[0] != '#' :
            cat = cat + ((line, line),)
      return cat
   except IOError:
      return (('err', 'Error'),)

aircraft.cat file listing:

# List of aircraft types
dont know
Airbus A310
Airbus A320
Airbus A330
Airbus A340
Airbus A380
Antonov An-148
Boeing 717
Boeing 737
Boeing 747
Boeing 757
Boeing 767
Boeing 777
Boeing 787
Bombardier CRJ-1000
Embraer 195
Evektor EV-55
Fokker 50
Fokker 100
Ilyushin Il-96
Tupolev Tu-204
Sukjoi Superjet-100

Implementing web view: add plane sighting

Define html template: add.html

{% load static %}
<head><title>Add a New Sighting</title>
<<body style='font-family:verdana;' onload="sightingPageLoaded()">
<table style="margin-left:auto; margin-right:auto;" >
<tr><th>New Plane Sighting</th></tr>
<!- Template input: spotters, locations, engLocChoice, sizeChoice, engineTypes
  enginePos, aircraftTypes ->
<tr><td><a href='/static/all.html'>View plane sightings</a></td></tr>
   <tr><th colspan='3' class='lalign_th'>Spotter Details</th></tr>
   <tr><td>Spotter ID</td><td><select id='spotterId' onchange="spotterIdChanged();" style="width:100%">
      <option value='0'>New</option>
      {% for spotter in spotters %}
         <option value='{{spotter.spotter_id}}'>{{ spotter.spotter_id }} - {{ spotter.name }}</option>
      {% endfor %}
   <tr><td class='ralign'>Name</td><td> <input id='spotterName' type='text' style="width:100%" /></td>
      <td class='redcross' id='valid_spotterName'></td> </tr>
   <tr><td class='ralign'>E-Mail</td><td> <input id='spotterEmail' type='text' style="width:100%" /></td>
      <td class='redcross' id='valid_spotterEmail'></td> </tr>

Link to view:

# Web UI for adding a new sighting
def add(request) :
   context = { 'spotters': Spotter.objects.all(),
               'locations' : Location.objects.all(),
               'engLocChoice' : Wing.LocationChoice,
               'sizeChoice' : Size.LENGTH_CHOICE,
               'engineTypes' : GetTypeChoice('engine'),
               'enginePos' : Engine.ENGINE_POSITION,
               'aircraftTypes': GetTypeChoice('aircraft'),
   return render(request, 'add.html', context)

Link to url

urlpatterns = [
   url(r'^$', RedirectView.as_view(url='/static/all.html')),
   url(r'^add/index/$', views.index, name='index'),
   url(r'^add/$', views.add, name='add_sighting'),
   url(r'^add/service/$', views.service, name='service'),
   url(r'^add/uploadfile/$', views.uploadfile, name='uploadfile'),
Implementing REST API
def service(request) :
   jdata = json.loads(request.body)
   cmd = jdata['cmd']
   sid = 0
   if cmd == 'get_spotter' :
      sid = int(jdata["id"])
      spotters = Spotter.objects.filter(spotter_id=sid)
      if len(spotters) > 0 :
         return JsonResponse({'name': spotters[0].name, 'email': spotters[0].email })
      else : return JsonResponse({'err': 'Invalid spotter id: ' + str(sid) })
   elif cmd == 'get_loc' :
      sid = int(jdata["id"])
      locations = Location.objects.filter(location_id=sid)
      if len(locations) > 0 :
         return JsonResponse({'name': locations[0].name, 'lat': locations[0].latitude,
                             'long': locations[0].longitude })
      else : return JsonResponse({'err': 'Invalid spotter id: ' + str(sid) })
   elif cmd == 'save_sighting' :
      sighting = Sighting()
      spotter_v = jdata['spotter'] # name, email
      spotter = Spotter.objects.filter(name=spotter_v['name']).first()
      if spotter == None :
         spotter = Spotter(name=spotter_v['name'], email=spotter_v['email'])
      sighting.spotter = spotter

      location_v = jdata['location'] # name, lat(f), long(f)
      loc = Location.objects.filter(name=location_v['name']).first()
      if loc == None :
         loc = Location(name=location_v['name'], latitude=location_v['lat'], longitude=location_v['long'])
      sighting.location = loc

      ddmmyy = jdata['date'].split('/') # dd/mm/yyyy
      hhmm = jdata['time'].split(':')  # hh:mm
      sighting.time = datetime(int(ddmmyy[2]), int(ddmmyy[1]), int(ddmmyy[0]), int(hhmm[0]), int(hhmm[1]))
      sighting.nvp = int(jdata['nvp'])

      acrft = jdata['aircraft'] # type, engine{} size{} wing{}
      acEngine = acrft['engine'] # type, number, positions, noise_desc, noise_audio
      acSize = acrft['size'] # length,wingspan,tail
      acWing = acrft['wing'] # number,position,swept

      kvargs = {'type': acEngine['type'], 'number': acEngine['number'], 'positions': acEngine['positions'],
                'noise_desc': acEngine['noise_desc'], 'noise_audio':acEngine['noise_audio']}
      engine = Engine.objects.filter(**kvargs).first()
      if engine == None :
         engine = Engine(**kvargs)

      kvargs = {'length':acSize['length'], 'wingspan':acSize['wingspan'], 'tailheight':acSize['tail']}
      size = Size.objects.filter(**kvargs).first()
      if size == None :
        size = Size(**kvargs)

      kvargs = {'number':acWing['number'], 'position':acWing['position'], 'swept':acWing['swept']}
      wing = Wing.objects.filter(**kvargs).first()
      if wing == None :
         wing = Wing(**kvargs)

      aircraft = Aircraft(type=acrft['type'], engine=engine, size=size, wing=wing)

      sighting.aircraft = aircraft
      sighting.markings = jdata['markings']
      sighting.photos = jdata['photos']

      fditamap = open(os.path.join(APP_ROOT, "dita", "sighting.ditamap"), 'w')
      fditamap.write('<?xml version="1.0" encoding="utf-8"?>\n')
      fditamap.write('<!DOCTYPE map PUBLIC "-//OASIS//DTD DITA Map//EN" "map.dtd">\n')
      fditamap.write('<map title="Plane Sightings">\n')

      all_sightings = Sighting.objects.all()
      for s in all_sightings :
         xml_path = saveSightingToDitaFile(s)
         fditamap.write('   <topicref href="sighting_' + str(s.id) + '.xml" type="reference"></topicref>\n')

      ditaThread(all_sightings).start()   # Process dita output

      return JsonResponse({'ok': 'Your sighting saved successfully (id=' + str(sighting.id) + ')'})

   return JsonResponse({'err': 'Error in processing command:' + cmd })

Link to url

urlpatterns = [
   url(r'^$', RedirectView.as_view(url='/static/all.html')),
   url(r'^add/index/$', views.index, name='index'),
   url(r'^add/$', views.add, name='add_sighting'),
   url(r'^add/service/$', views.service, name='service'),
   url(r'^add/uploadfile/$', views.uploadfile, name='uploadfile'),

Consuming the service API


Ads from Googlee
Dr Li Anchor Profi
Engineering anchorage plate design system
©Andrew Qu, 2016. All rights reserved. Code snippets may be used "AS IS" without any kind of warranty. DIY tips may be followed at your own risk.