Browse Source

Update v1.3

master
Evangelos Oulis 5 years ago
parent
commit
d3fe34b6ec
  1. 89
      project.adoc
  2. 44
      project.html
  3. 7
      serverNode/DB.sql
  4. 1
      serverNode/Procfile
  5. 3
      serverNode/requirements.txt
  6. 64
      serverNode/serv.py
  7. 73
      webInterface/parking.html

89
project.adoc

@ -25,30 +25,29 @@
== Smart Parking == Smart Parking
Το "Smart Parking" Έξυπνο πάρκινγκ βασίζεται στη διαδικασία όπου η κατάσταση του πάρκινγκ κοινοποιείται Το "Smart Parking" Έξυπνο πάρκινγκ βασίζεται στη διαδικασία όπου η κατάσταση του πάρκινγκ κοινοποιείται
μέσω ενός συνόλου hardware και software στο διαδίκτυο και έτσι πετυχαίνουμε η κατάσταση του να είναι διαθέσιμη μέσω ενός συνόλου hardware και software στο διαδίκτυο έτσι ώστε να πετυχουμε η κατάσταση του να είναι διαθέσιμη
"accesable" από το διαδίκτυο. Αυτό το χαρακτηριστικό κάνει αυτό το αντικειμένο μέρος του διαδικτύου και του κόσμου IoT. "accesable" από το διαδίκτυο. Αυτό το χαρακτηριστικό κάνει αυτό το αντικειμένο μέρος του διαδικτύου και του κόσμου του IoT.
*Η υλοποίηση του Smart Parking χωρίζεται σε 3 βασικά μέρη:* *Η υλοποίηση του Smart Parking χωρίζεται σε 4 βασικά μέρη:*
* Το 1~ο~ μέρος αποτελείται από ένα σύνολο αισθητήρων που εγκαθιστούντε σε κάθε θέση parking (sensor), τα οποία αποτελούνται * Το 1~ο~ μέρος αποτελείται από ένα σύνολο αισθητήρων (ultrasonic) που εγκαθιστούντε σε κάθε θέση parking (sensor) και
από έναν αισθητήρα μέτρησης απόστασης (ultrasonic) και έναν μικροελεγκτή (Arduino Uno), έτσι ώστε να ανιχνεύει και να κωδικοποιεί έναν μικροελεγκτή (Arduino Uno), έτσι ώστε να ανιχνεύει και να κωδικοποιεί για μία συγκεκριμένη θέση έαν υπάρχει
για μία συγκεκριμένη θέση έαν υπάρχει κάποιο όχημα ή όχι. κάποιο όχημα ή όχι.
* Το 2~ο~ μέρος αποτελείται από την συσκευή gateway σε Raspberry Pi1, η οποία διαβάζει στη σειριακή του * Το 2~ο~ μέρος αποτελείται από τον συσκευή gateway σε Raspberry Pi1, η οποία διαβάζει στη σειριακή του
τη πληροφορία από το Arduino Uno, που κάνει sense μία θέση parking, και στέλνει αυτή την πληροφορία τη πληροφορία από το Arduino Uno, που κάνει "sense" μία θέση parking, και στέλνει αυτή την πληροφορία
σε έναν web server με χρήση REST API. σε έναν web server με χρήση REST API.
* To 3~o~ μέρος αποτελείται από τον WEB Server ο οποίος αποτελείται από ένα process γραμμένο σε python που υλοποιεί * To 3~o~ μέρος αποτελείται από τον WEB Server ο οποίος αποτελείται από ένα process γραμμένο σε python. Το proccess αυτό υλοποιεί
ένα REST API έτσι ώστε να μπορεί να αποθηκεύει την κατάσταση κάθε θέσης parking σε μία δομή λίστας με χαρακτηριστικό έναν REST API WEB Server έτσι ώστε να μπορεί να αποθηκεύει την κατάσταση κάθε θέσης parking σε μία δομή λίστας με χαρακτηριστικό
κλειδί τον κωδικό κάθε θέσης parking. κλειδί τον κωδικό κάθε θέσης parking.
* Το 4~o~ μέρος αποτελείται από την διεπαφή (Interface), με την οποία μέσω web σελίδας βλέπει κανείς την κατάσταση * Το 4~o~ μέρος αποτελείται από την διεπαφή χρήστη (Interface), η οποία μέσω WEB σελίδας βλέπει κανείς την κατάσταση
του parking, δηλαδή πόσες και ποιές θέσεις μέσα στον χώρο είναι ελεύθερες. του parking, δηλαδή πόσες και ποιές θέσεις μέσα στον χώρο είναι ελεύθερες.
=== Parking Sensor Node === Parking Sensor Node (1~ο~ μέρος)
==== Υλικά Κόμβου ==== Υλικά Κόμβου
* 1 x Bread-Board
* 1 x Arduino Uno * 1 x Arduino Uno
* 1 x Red led * 1 x Red led
* 1 x Blue led * 1 x Blue led
@ -56,10 +55,10 @@
* 1 x Ultrasonic Sensor * 1 x Ultrasonic Sensor
==== Υλοποίηση του Parking Sensor ==== Υλοποίηση του Parking Sensor
Σε κάθε θέση parking υπάρχει ένας κόμβος απότελούμενος από ένα Arduino και έναν αισθητήρα Σε κάθε θέση parking υπάρχει ένας κόμβος που αποτελείται από ένα Arduino Uno και έναν αισθητήρα
απόστασης (ultrasonic) εγκατεστημένος πάνω στο Arduino. Η λειτουργία αυτού βασίζεται στη απόστασης (ultrasonic) εγκατεστημένος πάνω σε μικροελεγκτή Arduino Uno. Η λειτουργία αυτού βασίζεται στη
μέτρηση της απόστασης από τον κόμβο μέχρι κάποιο αντικείμενο (αυτοκίνητο) που εμποδίζει τη θέση του parking μέτρηση της απόστασης από τον κόμβο μέχρι κάποιο αντικείμενο (πιθανό αυτοκίνητο) που εμποδίζει τη θέση του parking,
και την εξαγωγή της κατάσταασης της αυτής της θέσης στη σειριακή θύρα του Arduino. καθώς και την εξαγωγή της κατάσταασης αντίστοιχης θέσης στη σειριακή θύρα του Arduino.
[.float-group] [.float-group]
-- --
@ -87,9 +86,10 @@ image::Photos/arduino2.jpg[300,200]
==== Διασύνδεση Κόμβου ==== Διασύνδεση Κόμβου
Ο κόμβος αυτός συνδέεται με ένας "Gateway" κόμβο (βασισμένος σε Raspberry Pi) ο οποίος είναι υπεύθυνος για την Ο κόμβος αυτός συνδέεται με ένας "Gateway" κόμβο (βασισμένος σε Raspberry Pi) ο οποίος είναι υπεύθυνος για την
μετάδοση της πληροφορίας που αφορά την διαθεσιμότητα της θέσης του parking στο διαδίκτυο. Η πληροφορία αυτή μετάδοση της πληροφορίας που αφορά την διαθεσιμότητα της θέσης του parking στο διαδίκτυο. Η πληροφορία αυτή
λαμβάνεται στον "Gateway" κόμβο οποίος στη συνέχεια την αποκωδικοποιεί και την αποστέλει στον WEB server μέσω του διαδικτύου. λαμβάνεται στον "Gateway" κόμβο ο οποίος στη συνέχεια την αποκωδικοποιεί και την αποστέλει στον WEB server μέσω του διαδικτύου.
=== Gateway Node
=== Gateway Node (2~ο~ μέρος)
==== Υλικά Κόμβου ==== Υλικά Κόμβου
* 1 x Raspberry Pi 1 * 1 x Raspberry Pi 1
@ -100,19 +100,29 @@ image::Photos/arduino2.jpg[300,200]
==== Υλοποίηση και Προγραμματισμός ==== Υλοποίηση και Προγραμματισμός
Η υλοποίηση αποτελείται από την εγκατάσταση του Raspbian OS στο Raspberry και τη δημιουργία ενός proccess Η υλοποίηση αποτελείται από την εγκατάσταση του Raspbian OS στο Raspberry και τη δημιουργία ενός proccess
σε γλώσσα python το οποίο διαβάζει από την σειριακή θύρα του την πληροφορία που λαμβάνει από το Arduino με σε γλώσσα Python. Το process αυτό διαβάζει από την σειριακή θύρα του την πληροφορία που λαμβάνει από το αντίστοιχο Arduino Uno με
την μορφή <κωδικός θέσης>#<διαθεσιμότητα 0 ή 1>. Ύστερα αποκωδικοποιεί αυτή την πληροφορία η οποία περιγράφει τον κωδικό της θέσης την μορφή <κωδικός θέσης>#<διαθεσιμότητα 0 ή 1>. Ύστερα αποκωδικοποιεί αυτή την πληροφορία η οποία περιγράφει τον κωδικό της θέσης
και την διαθεσιμότητα της και την αποστέλει μέσω ενός REST API με την μέθοδο POST σε έναν WEB Server. και την διαθεσιμότητα της και την αποστέλει μέσω ενός REST API με την μέθοδο POST σε έναν WEB Server. Τα δεδομέμα μας σε αυτήν
την επικοινωνία παίρνουν μία μορφή JSON (JavaScript Object Notation).
==== Διασύνδεηση στο Διαδίκτυο ==== Διασύνδεηση στο Διαδίκτυο
Ο κόμβος Gateway έχει διασύνδεση με το διαδίκτυο μέσω ενός καλωδίου Ethernet (UTP) έτσι ώστε να μπορέσει Ο κόμβος Gateway έχει διασύνδεση με το διαδίκτυο μέσω ενός καλωδίου Ethernet (UTP) έτσι ώστε να μπορέσει
να στείλει την πληροφορία να στείλει την πληροφορία στο διαδίκτυο.
== Server Node (3~ο~ μέρος - Κεντρικός Server όπου κρατά την κατάσταση της κάθε θέσης του Parking)
Ο κόμβος αυτός υλοποιεί ένα process γραμμένο σε γλώσσα προγραμματισμού Python 3. Αυτό το process εκτελεί ένα
REST API έτσι ώστε να μπορούν να επικοινωνούν εύκολα πολλοί Gateway κόμβοι. Στην είσοδό του και στην έξοδό του
τα δεδομένα μας έχουν την μορφή JSON.
== Server Node (Κεντρικός Server όπου κρατά την κατάσταση της κάθε θέσης του Parking) Ο server αποθηκεύει όλα τα απαραίτητα δεδομένα σε μία Βάση δεδομένων MySQL, η οποία διαθέτει ένα πίνακα.
Ο κόμβος αυτός υλοποιεί ένα process γραμμένο σε γλώσσα προγραμματισμού Python 3. Ο πίνακας κρατά όλα τα απαραίτητα πεδία που είναι:
* Τον κωδικό της θέσης parking
* Την διαθεσιμότητά της αντίστοιχης θέση (0 ή 1)
=== Εκτέλεση του Process στο Cloud === Εκτέλεση του Process στο Cloud
Για την εκτέλεση του process χρησιμοποιούμε την πλατφόρμα IAAS (Infrastructure as a Service) Για την εκτέλεση του process χρησιμοποιούμε μία πλατφόρμα IAAS (Infrastructure as a Service) ονόματι
link:++https://www.heroku.com/platform++[Heroku], για την οποιά μπορούμε να βρούμε περεταίρω πληροφορίες στον σύνδεσμο link:++https://www.heroku.com/platform++[Heroku], για την οποιά μπορούμε να βρούμε περεταίρω πληροφορίες στον σύνδεσμο
παραπάνω. παραπάνω.
@ -124,12 +134,35 @@ image::Photos/itops-pizza_as_a_service.png[1000,800]
-- --
Για την διαδικασία του deployment εκτελούμε ένα σύνολο βημάτων τα οποία αποτελούνται από την αντιγραφή του κώδικα Για την διαδικασία του deployment εκτελούμε ένα σύνολο βημάτων τα οποία αποτελούνται από την αντιγραφή του κώδικα
σε ένα reposetory του link:++https://github.com/++[GitHub] και την δημιουργία ενός project στην πλατφόρμα για το σε ένα reposetory του link:++https://github.com/oulievancs/serverNode++[GitHub] και την δημιουργία ενός project στην πλατφόρμα για το
τρέξιμο του process. link:++https://stackabuse.com/deploying-a-flask-application-to-heroku/++[περισσότερα] τρέξιμο του process. link:++https://stackabuse.com/deploying-a-flask-application-to-heroku/++[περισσότερα]. Ακόμα
εγκαθισούμε στο project που μόλις φτιάξαμε μία MySQL βάση δεδομένων για να μπορούμε να αποθηκεύσουμε τα δεδομένα μας.
==== Deployment
* Το πρώτο πράγμα που χρειαζόμαστε είναι όλες οι απαραίτητες βιβλιοθήκες που χρησιμοποιεί ο κώδικας, έτσι * Το πρώτο πράγμα που χρειαζόμαστε είναι όλες οι απαραίτητες βιβλιοθήκες που χρησιμοποιεί ο κώδικας, έτσι
ώστε να γνωρίζει το Heroku τι να μας προσφέρει. Αυτό επιτυγχάνεται με την αρχειοθέτηση αυτών σε ένα αρχείο ώστε να γνωρίζει το Heroku τι να μας προσφέρει. Αυτό επιτυγχάνεται με την αρχειοθέτηση αυτών σε ένα αρχείο
ονόματι re ονόματι requirements.txt .
* Έπειτα την δημιουργία ενός αρχείου που περιγράφει το που βρίσκεται η κύρια συνέρτησή μας (main) για την
έναρξη του process. Αυτό το αρχείο ονομάζεται Procfile . Στο αρχείο αυτό αναφέτεται ένα gunicorn module.
Ο gunicorn είναι ένας Python HTTP WEB server. Αυτό ουσιαστικά είναι ο ο πυρήνας για την εκτέλεση του API μας.
* Έπειτα με μια απομακρυσμένη σύνδεση στη βάση μας της οποίας τα στοιχεία πρόσβασης γαίνονται στο Heroku,
πραγματοποιούμε μία σύνδεση και δημιουργούμε τον πίνακά μας για την αποθήκευση.
==== REST API
Το REST API ουσιαστικά σηκώνει δύο υπηρεσίες. Αυτές είναι:
* / [GET]: που πας επιστρέφει για κάθε θέση του parking αν είναι διαθέσιμη ή όχι κωδικοποιημένα με 0 ή 1.
Στο response τα δεδομένα μας παίρνουν μορφή JSON. Τα δεδομένα που επιστρέφει γίνονται fetch από τη βάση δεδομένων.
* /parkingStatus [POST]: που μας επιτρέπει να αλλάξουμε την κατάσταση μίας θέσης parking. Το POST των δεδομένων
στο body γίνεται με την JSON αναπαράστασή τους έτσι ώστε να μπορέσει ο Server να τα επεξεργαστεί, ο οποίος στη
συνέχεια αποθηκεύει την νέα θέση στη Βάση δεδομένων.
== Διεπαφή Χρήστη (4~ο~ μέρος)
Η διεπαφή του χρήσρη π
== Autonomous Parking == Autonomous Parking

44
project.html

File diff suppressed because one or more lines are too long

7
serverNode/DB.sql

@ -0,0 +1,7 @@
DROP TABLE IF EXISTS PARKING;
CREATE TABLE IF NOT EXISTS PARKING (
PARKING_CODE INT(4) NOT NULL UNIQUE,
PARKING_STATUS BOOLEAN NOT NULL
);

1
serverNode/Procfile

@ -0,0 +1 @@
web: gunicorn serv:app --preload --timeout 150000

3
serverNode/requirements.txt

@ -1,4 +1,5 @@
flask flask
flask_restful flask_restful
flask_cors flask_cors
gunicorn==19.9.0
mysql-connector

64
serverNode/serv.py

@ -6,6 +6,7 @@ from flask_restful import Resource, Api
from json import dumps from json import dumps
import json import json
from flask_cors import CORS from flask_cors import CORS
import mysql.connector
# ================================================================== # ==================================================================
# ================================================================== # ==================================================================
@ -17,7 +18,29 @@ CORS(app)
# creating an API object # creating an API object
api = Api(app) api = Api(app)
parks = dict() # Initialize the database Connection
mydb = mysql.connector.connect(
host = "q2gen47hi68k1yrb.chr7pe7iynqr.eu-west-1.rds.amazonaws.com",
user = "zsgmj50h7zgz9ioq",
password = "omk5l1hrwsgvlcez",
database = "g0s9cnmdkziq6fsp"
)
myCursor = mydb.cursor()
# ==================================
# Define our functions.
# Define a function that gets the parking status
# for all parking codes.
def getParkings():
parks = dict()
myCursor.execute("SELECT * FROM PARKING")
myRes = myCursor.fetchall()
for res in myRes:
parks[res[0]] = res[1]
return parks
# ================================================================== # ==================================================================
# making a class for a particular resource # making a class for a particular resource
@ -26,6 +49,7 @@ parks = dict()
# other methods include put, delete, etc. # other methods include put, delete, etc.
class Parking(Resource): class Parking(Resource):
def get(self): def get(self):
parks = getParkings()
return parks, 200 return parks, 200
class ParkingStatus(Resource): class ParkingStatus(Resource):
@ -35,15 +59,43 @@ class ParkingStatus(Resource):
<body><h1>Not get at '/parkingStatus'.</h1></body> <body><h1>Not get at '/parkingStatus'.</h1></body>
</html>""" </html>"""
def post(self): def post(self):
print (request) # Gets the data into a JSON Object.
data = json.loads(request.data) data = json.loads(request.data)
print (data)
parks[data['no']] = data['status'] # SQL get all Parking places status.
parks = getParkings()
thereIs = False
toUpdate = False
try:
if parks[int(data['no'])] != int(data['status']):
toUpdate = True
thereIs = True
except IndexError:
# handle Index Error
thereIs = False
except KeyError:
# handle the KeyError
thereIs = False
if not thereIs:
# Make a new insert entry for a new Parking Code.
values = (int(data['no']), int(data['status']))
myCursor.execute("INSERT INTO PARKING (PARKING_CODE, PARKING_STATUS) VALUES (%s, %s)", values)
mydb.commit()
parks = getParkings()
elif toUpdate:
# Make an Update status for Parking Code that availability changed.
values = (int(data['status']), int(data['no']))
myCursor.execute("UPDATE PARKING SET PARKING_STATUS=%s WHERE PARKING_CODE=%s", values)
mydb.commit()
parks = getParkings()
return parks[data['no']], 201 return parks[data['no']], 201
# ================================================================== # ==================================================================
# adding the defined resources along with their corresponding urls # adding the defined resources along with their corresponding urls to REST APIs
api.add_resource(Parking, '/') api.add_resource(Parking, '/')
api.add_resource(ParkingStatus, '/parkingStatus') api.add_resource(ParkingStatus, '/parkingStatus')
@ -53,7 +105,7 @@ if __name__ == '__main__':
app.run( app.run(
debug=True, debug=True,
host=app.config.get("HOST", "0.0.0.0"), host=app.config.get("HOST", "0.0.0.0"),
port=app.config.get("PORT", "8080") port=app.config.get("PORT", "5000")
) )
# END # END

73
webInterface/parking.html

@ -21,6 +21,54 @@ $(document).ready(function(){
if (server_ip !== "" && server_ip !== null && server_port !== "" && server_port !== null && server_protocol !== "" && server_protocol != null) { if (server_ip !== "" && server_ip !== null && server_port !== "" && server_port !== null && server_protocol !== "" && server_protocol != null) {
$("#msg").html("<font color='red'>ok!</font>"); $("#msg").html("<font color='red'>ok!</font>");
$("#approve").prop("disabled", true);
jQuery.ajax({
url: server_protocol + "://" + server_ip + ":" + server_port + "/",
type: "GET",
contentType: "application/json; charset=utf-8",
success: function(resultData) {
row = 1, pos = "r";
$("talbe#2").append("<tr id=\"row0\"></tr>");
$("tr#row0").append("<td width=\"30%\" bgcolor=\"lightgrey\"><div class=\"road\"></div></td>");
$("tr#row0").append("<td width=\"20%\" bgcolor=\"lightgrey\"><div class=\"road\"></div></td>");
$("tr#row0").append("<td width=\"20%\" bgcolor=\"lightgrey\"><div class=\"road\"></div></td>");
$("tr#row0").append("<td width=\"30%\" bgcolor=\"lightgrey\"><div class=\"road\"></div></td>");
$.each(resultData, function(key, val) {
console.log("Key " + key + ", val: " + val);
content = "";
if (val == 0) {
if (pos == "l") {
content = "<td id=\"park" + key + "\" width=\"30%\">No" + key + "<div id=\"" + key + "\" class=\"full left\"></div></td>";
} else {
content = "<td id=\"park" + key + "\" width=\"30%\">No" + key + "<div id=\"" + key + "\" class=\"full\"></div></td>";
}
} else {
if (pos == "l") {
content = "<td id=\"park" + key + "\" width=\"30%\">No" + key + "<div id=\"" + key + "\" class=\"empty left\"></div></td>";
}
else {
content = "<td id=\"park" + key + "\" width=\"30%\">No" + key + "<div id=\"" + key + "\" class=\"empty\"></div></td>";
}
}
if (pos == "r") {
$("table#tab2").append("<tr id=\"row" + row + "\"></tr>");
$("tr#row" + row).append(content);
$("tr#row" + row).append("<td width=\"20%\" bgcolor=\"lightgrey\"><div class=\"road\"></div></td>");
pos = "l";
} else if (pos == "l") {
$("tr#row" + row).append("<td width=\"20%\" bgcolor=\"lightgrey\"><div class=\"road\"></div></td>");
$("tr#row" + row).append(content);
row++;
pos = "r";
}
});
},
error: function(jqXHR, testStatus, errorThrown) {
},
timeout: 120000,
});
setInterval(function () { setInterval(function () {
jQuery.ajax({ jQuery.ajax({
url: server_protocol + "://" + server_ip + ":" + server_port + "/", url: server_protocol + "://" + server_ip + ":" + server_port + "/",
@ -31,15 +79,15 @@ $(document).ready(function(){
$.each(resultData, function(key, val) { $.each(resultData, function(key, val) {
console.log(key + " -> " + val); console.log(key + " -> " + val);
if (val == "1") if (val == "1")
$("#" + key).removeClass("full").addClass("empty"); $("div#" + key).removeClass("full").addClass("empty");
else if (val == "0") else if (val == "0")
$("#" + key).removeClass("empty").addClass("full"); $("div#" + key).removeClass("empty").addClass("full");
}); });
}, },
error: function(jqXHR, testStatus, errorThrown) { error: function(jqXHR, testStatus, errorThrown) {
}, },
timeout: 12000, timeout: 120000,
}); });
}, 1000); }, 1000);
} else { } else {
@ -47,37 +95,42 @@ $(document).ready(function(){
} }
}); });
}); });
</script>
<script>
</script> </script>
</head> </head>
<body> <body>
<h1>Parking</h1> <h1>Parking</h1>
<h3>Server Settings</h3> <h3>Server Settings</h3>
<table> <form autocomplete="on">
<table id="tab1">
<tr> <tr>
<td>Server Protocol:</td> <td>Server Protocol:</td>
<td><select type="text" id="server_protocol"> <td><select type="text" id="server_protocol">
<option value="http">http</option>
<option value="https">https</option> <option value="https">https</option>
<option value="http">http</option>
<td> <td>
</tr> </tr>
<tr> <tr>
<td>Server IP:</td> <td>Server IP:</td>
<td><input type="text" id="server_ip" placeholder="xxx.xxx.xxx.xxx"/></td> <td><input type="text" id="server_ip" placeholder="xxx.xxx.xxx.xxx" value="iot-smart-parking.herokuapp.com"/></td>
</tr> </tr>
<tr> <tr>
<td>Server PORT:</td> <td>Server PORT:</td>
<td><input type="text" id="server_port" placeholder="xxxx"/></td> <td><input type="text" id="server_port" placeholder="xxxx" value="443" /></td>
</tr> </tr>
<tr> <tr>
<td><div id="msg"></div></td> <td><div id="msg"></div></td>
<td><input type="button" id="approve" value="ok"/></td> <td><input type="button" id="approve" value="ok"/></td>
</tr> </tr>
</table> </table>
</form>
<h3>Parking Diagram</h3> <h3>Parking Diagram</h3>
<table width="100%"> <table width="100%" id="tab2">
<tr> <!--<tr>
<td id="park4" width="30%">No4<div id="4" class="full"></div></td> <td id="park4" width="30%">No4<div id="4" class="full"></div></td>
<td width="20%" bgcolor="lightgrey"><div class="road"></div></td> <td width="20%" bgcolor="lightgrey"><div class="road"></div></td>
<td width="20%" bgcolor="lightgrey"><div class="road"></div></td> <td width="20%" bgcolor="lightgrey"><div class="road"></div></td>
@ -100,7 +153,7 @@ $(document).ready(function(){
<td width="20%" bgcolor="lightgrey"><div class="road"></div></td> <td width="20%" bgcolor="lightgrey"><div class="road"></div></td>
<td width="20%" bgcolor="lightgrey"><div class="road"></div></td> <td width="20%" bgcolor="lightgrey"><div class="road"></div></td>
<td id="park8" width="30%">No8<div id="8" class="full left"></div></td> <td id="park8" width="30%">No8<div id="8" class="full left"></div></td>
</tr> </tr>-->
</table> </table>
</body> </body>
</html> </html>

Loading…
Cancel
Save