private static void drawEventsHod(SvgFile svg, List<UserData> users, Map<Integer, Zone> zones) {
   float strokeWidth = 0.5f;
   int ci = 0;
   for (UserData user : users) {
     for (Event e : user.getEvents()) {
       //				e.get
       Float hod = e.getHourOfDay();
       if (hod == null) continue;
       String stroke = getColour(scaleHod(hod));
       if (e.getGpsTagId() != 0) {
         Zone z = zones.get(e.getGpsTagId());
         if (z != null) {
           String fill = getZoneFill(z);
           float rx = (float) ((Math.random() - 0.5) * JITTER);
           float ry = (float) ((Math.random() - 0.5) * JITTER);
           float size = scaleD(z.getRadius());
           if (size < 1) size = 1;
           svg.circle(
               rx + scaleX(Mercator.mercX(z.getLon())),
               ry + scaleY(Mercator.mercY(z.getLat())),
               size,
               stroke,
               1.0f,
               fill,
               "fill-opacity=\"0.1\"");
         }
       }
     }
   }
 }
 private static void drawEvents(
     SvgFile svg, List<UserData> users, String overrideStroke, Map<Integer, Zone> zones) {
   float strokeWidth = 0.5f;
   int ci = 0;
   for (UserData user : users) {
     ci = ci + 1 % colors.length;
     String stroke = overrideStroke != null ? overrideStroke : colors[ci];
     for (Event e : user.getEvents()) {
       //				e.get
       if (e.getGpsTagId() != 0) {
         Zone z = zones.get(e.getGpsTagId());
         if (z != null) {
           String fill = getZoneFill(z);
           float rx = (float) ((Math.random() - 0.5) * JITTER);
           float ry = (float) ((Math.random() - 0.5) * JITTER);
           svg.circle(
               rx + scaleX(Mercator.mercX(z.getLon())),
               ry + scaleY(Mercator.mercY(z.getLat())),
               scaleD(z.getRadius()),
               stroke,
               1.0f,
               fill,
               "fill-opacity=\"0.1\"");
         }
       }
     }
   }
 }
 private static void drawZones2(SvgFile svg, Map<Integer, Zone> zones) {
   for (Zone z : zones.values()) {
     String fill = getZoneFill(z);
     float size = scaleD(z.getRadius());
     if (size < 1) size = 1;
     svg.circle(
         scaleX(Mercator.mercX(z.getLon())),
         scaleY(Mercator.mercY(z.getLat())),
         size,
         fill,
         0.0f,
         fill,
         " fill-opacity=\"15%\" stroke-opacity=\"20%\"");
   }
 }
 /**
  * @param file
  * @return
  * @throws FileNotFoundException
  * @throws UnsupportedEncodingException
  */
 private static SvgFile createBackgroundFile(File file)
     throws UnsupportedEncodingException, FileNotFoundException {
   SvgFile svg = new SvgFile(file, MAG);
   svg.desc("DrawTrails of automics data, " + new Date());
   svg.print("<filter id=\"desaturate\"  primitiveUnits=\"objectBoundingBox\">");
   //		svg.print("<feImage x=\""+(500-width/2)+"\" y=\""+(500-width/2)+"\" width=\""+width+"\"
   // height=\""+width+"\" xlink:href=\"altontowers-zoom15.png\"/>");
   svg.print(
       "<feImage x=\"0%\" y=\"0%\" width=\"100%\" height=\"100%\" xlink:href=\"altontowers-zoom15.png\"/>");
   svg.print(
       "<feColorMatrix type=\"matrix\" values=\"0.3333 0.3333 0.3333 0 0 "
           + "0.3333 0.3333 0.3333 0 0 "
           + "0.3333 0.3333 0.3333 0 0 "
           + "0      0      0      1 0\"/>");
   svg.print(
       "<feColorMatrix type=\"matrix\" values=\"0.3 0 0 0 0.7 "
           + "0 0.3 0 0 0.7 "
           + "0 0 0.3 0 0.7 "
           + "0      0      0      1 0\"/>");
   svg.print("</filter>");
   // zoom level - 2^zoom level tiles in x & y
   int zoom = 15;
   double imageWidth = 2 * Mercator.mercX(180) / Math.pow(2, zoom) * 640 / 256;
   svg.print(
       "<rect x=\""
           + MAG * SCALE * 0.5 * (1 - imageWidth / width)
           + "\" y=\""
           + MAG * SCALE * 0.5 * (1 - imageWidth / width)
           + "\" width=\""
           + MAG * SCALE * (imageWidth / width)
           + "\" height=\""
           + MAG * SCALE * (imageWidth / width)
           + "\" filter=\"url(#desaturate)\"/>");
   return svg;
 }
  /** @param args */
  public static void main(String[] args) {
    if (args.length != 3) {
      logger.log(Level.SEVERE, "usage: <logdir> <zonefile> <outdir>");
      System.exit(-1);
    }
    try {
      cLat = 52.987253;
      cLon = -1.8895595;
      cX = Mercator.mercX(cLon);
      cY = Mercator.mercY(cLat);
      // http://maps.googleapis.com/maps/api/staticmap?center=40.714728,-73.998672&zoom=12&size=400x400&maptype=satellite&sensor=false
      // INFO: lon: -1.896922 to -1.882197, lat: 52.984308 to 52.990198
      // http://maps.googleapis.com/maps/api/staticmap?center=52.987253,-1.8895595&zoom=14&size=640x640&maptype=satellite&sensor=false
      List<UserData> users = LogReader.readLogs(new File(args[0]));
      Map<Integer, Zone> zones = LogReader.readZones(new File(args[1]));
      logger.log(Level.INFO, "Read " + zones.size() + " zones");
      File outdir = new File(args[2]);
      if (!outdir.isDirectory()) {
        logger.log(Level.SEVERE, "Output directory not valid: " + outdir);
        System.exit(-1);
      }

      width = 2000;

      SvgFile svg = createBackgroundFile(new File(outdir, "park.svg"));
      {
        double minX = Double.MAX_VALUE,
            minY = Double.MAX_VALUE,
            maxX = -Double.MAX_VALUE,
            maxY = -Double.MAX_VALUE;
        double minLat = Double.MAX_VALUE,
            minLon = Double.MAX_VALUE,
            maxLat = -Double.MAX_VALUE,
            maxLon = -Double.MAX_VALUE;
        float minHOD = Float.MAX_VALUE, maxHOD = -Float.MAX_VALUE;
        float minHOD2 = Float.MAX_VALUE, maxHOD2 = -Float.MAX_VALUE;
        for (UserData user : users) {
          for (Position p : user.getPositions()) {
            if (p.isTruncated()) continue;

            double x = p.getX();
            double y = p.getY();
            if (x < minX) minX = x;
            if (x > maxX) maxX = x;
            if (y < minY) minY = y;
            if (y > maxY) maxY = y;
            double lat = p.getLat();
            double lon = p.getLon();
            if (lat < minLat) minLat = lat;
            if (lat > maxLat) maxLat = lat;
            if (lon < minLon) minLon = lon;
            if (lon > maxLon) maxLon = lon;
            float hod = p.getHourOfDay();
            if (hod < minHOD) minHOD = hod;
            if (hod > maxHOD) maxHOD = hod;
          }
          for (Event e : user.getEvents()) {
            Float hod = e.getHourOfDay();
            if (hod == null) continue;
            if (hod < minHOD2) minHOD2 = hod;
            if (hod > maxHOD2) maxHOD2 = hod;
          }
        }
        logger.log(
            Level.INFO, "Ranges, x: " + minX + " to " + maxX + ", y: " + minY + " to " + maxY);
        logger.log(
            Level.INFO,
            "Ranges, lon: " + minLon + " to " + maxLon + ", lat: " + minLat + " to " + maxLat);
        logger.log(Level.INFO, "Ranges, HOD: " + minHOD + " to " + maxHOD);
        logger.log(Level.INFO, "Ranges, HOD2: " + minHOD2 + " to " + maxHOD2);
        minX -= BORDER_M;
        minY -= BORDER_M;
        maxX += BORDER_M;
        maxY += BORDER_M;
      }
      // String stroke = "#ff0000";
      drawPositions(svg, users, "#222", false);
      drawZones(svg, zones);

      svg.close();
      svg = createBackgroundFile(new File(outdir, "allusers.svg"));
      drawPositions(svg, users, null, false);
      svg.close();

      svg = createBackgroundFile(new File(outdir, "allevents.svg"));
      drawPositions(svg, users, null, false);
      drawEvents(svg, users, null, zones);
      svg.close();

      svg.close();
      svg = createBackgroundFile(new File(outdir, "allusershod.svg"));
      drawPositionsHod(svg, users, false);
      drawEventsHod(svg, users, zones);
      svg.close();

      svg = createBackgroundFile(new File(outdir, "allusershodtl.svg"));
      drawZones2(svg, zones);
      drawPositionsHod(svg, users, false);
      drawTimelineEventsHod(svg, users, zones);
      svg.close();

      Set<String> groups = new HashSet<String>();
      for (UserData u : users) {
        groups.add(u.getTrialid());
      }
      for (String group : groups) {
        logger.info("Group " + group);
        svg = createBackgroundFile(new File(outdir, "allusershod-" + group + ".svg"));
        List<UserData> members = new LinkedList<UserData>();
        for (UserData u : users) if (u.getTrialid().equals(group)) members.add(u);
        drawPositionsHod(svg, members, false);
        drawEventsHod(svg, members, zones);
        svg.close();

        svg = createBackgroundFile(new File(outdir, "allusershodtl-" + group + ".svg"));
        drawZones2(svg, zones);
        drawPositionsHod(svg, members, false);
        drawTimelineEventsHod(svg, members, zones);
        svg.close();
      }
      for (UserData user : users) {
        logger.info("User " + user.getTrialid() + " " + user.getTrialuserid());
        svg =
            createBackgroundFile(
                new File(
                    outdir,
                    "allusershod-" + user.getTrialid() + "-" + user.getTrialuserid() + ".svg"));
        List<UserData> members = new LinkedList<UserData>();
        members.add(user);
        drawPositionsHod(svg, members, false);
        drawEventsHod(svg, members, zones);
        svg.close();

        svg =
            createBackgroundFile(
                new File(
                    outdir,
                    "allusershodtl-" + user.getTrialid() + "-" + user.getTrialuserid() + ".svg"));
        drawZones2(svg, zones);
        drawPositionsHod(svg, members, false);
        drawTimelineEventsHod(svg, members, zones);
        svg.close();
      }

    } catch (Exception e) {
      logger.log(Level.SEVERE, "Error creating " + args[1], e);
    }
  }